early-access version 1818

This commit is contained in:
pineappleEA 2021-06-24 01:31:27 +02:00
parent 11b1d1062a
commit 8861a4d061
108 changed files with 53166 additions and 8823 deletions

View File

@ -172,7 +172,7 @@ macro(yuzu_find_packages)
set(REQUIRED_LIBS set(REQUIRED_LIBS
# Cmake Pkg Prefix Version Conan Pkg # Cmake Pkg Prefix Version Conan Pkg
"Catch2 2.13 catch2/2.13.0" "Catch2 2.13 catch2/2.13.0"
"fmt 7.1 fmt/7.1.2" "fmt 8.0 fmt/8.0.0"
"lz4 1.8 lz4/1.9.2" "lz4 1.8 lz4/1.9.2"
"nlohmann_json 3.8 nlohmann_json/3.8.0" "nlohmann_json 3.8 nlohmann_json/3.8.0"
"ZLIB 1.2 zlib/1.2.11" "ZLIB 1.2 zlib/1.2.11"

View File

@ -1,7 +1,7 @@
yuzu emulator early access yuzu emulator early access
============= =============
This is the source code for early-access 1817. This is the source code for early-access 1818.
## Legal Notice ## Legal Notice

View File

@ -1,6 +1,6 @@
<!-- Please read the contribution guidelines before submitting a pull request. --> <!--
<!-- By submitting this pull request, you agree that your contributions are licensed under the {fmt} license, Please read the contribution guidelines before submitting a pull request:
and agree to future changes to the licensing. --> https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
<!-- If you're a first-time contributor, please acknowledge it by leaving the statement below. --> By submitting this pull request, you agree that your contributions are licensed
under the {fmt} license, and agree to future changes to the licensing.
I agree that my contributions are licensed under the {fmt} license, and agree to future changes to the licensing. -->

View File

@ -0,0 +1,23 @@
name: doc
on: [push, pull_request]
jobs:
build:
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: |
sudo apt install doxygen python3-virtualenv
sudo npm install -g less clean-css
cmake -E make_directory ${{runner.workspace}}/build
- name: Build
working-directory: ${{runner.workspace}}/build
env:
KEY: ${{secrets.KEY}}
run: $GITHUB_WORKSPACE/support/build-docs.py

View File

@ -0,0 +1,78 @@
name: linux
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
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++-10
build_type: Debug
std: 17
os: ubuntu-18.04
- cxx: g++-10
build_type: Debug
std: 20
os: ubuntu-20.04
- cxx: clang++-9
build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17
os: ubuntu-18.04
- 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
- name: Create Build Environment
run: |
${{matrix.install}}
sudo apt install locales-all
cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
working-directory: ${{runner.workspace}}/build
env:
CXX: ${{matrix.cxx}}
CXXFLAGS: ${{matrix.cxxflags}}
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
threads=`nproc`
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: ctest -C ${{matrix.build_type}}
env:
CTEST_OUTPUT_ON_FAILURE: True

View File

@ -0,0 +1,37 @@
name: macos
on: [push, pull_request]
jobs:
build:
runs-on: macos-10.15
strategy:
matrix:
build_type: [Debug, Release]
include:
- shared: -DBUILD_SHARED_LIBS=ON
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
threads=`sysctl -n hw.logicalcpu`
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: ctest -C ${{matrix.build_type}}
env:
CTEST_OUTPUT_ON_FAILURE: True

View File

@ -0,0 +1,60 @@
name: windows
on: [push, pull_request]
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
matrix:
# windows-2016 and windows-2019 have MSVC 2017 and 2019 installed
# respectively: https://github.com/actions/virtual-environments.
os: [windows-2016, windows-2019]
platform: [Win32, x64]
build_type: [Debug, Release]
standard: [11, 17, 20]
include:
- os: windows-2016
platform: Win32
build_type: Debug
shared: -DBUILD_SHARED_LIBS=ON
exclude:
- os: windows-2016
platform: Win32
- os: windows-2016
standard: 17
- os: windows-2016
standard: 20
- os: windows-2019
standard: 11
- os: windows-2019
standard: 20
platform: Win32
steps:
- uses: actions/checkout@v2
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
- name: Configure
# Use a bash shell for $GITHUB_WORKSPACE.
shell: bash
working-directory: ${{runner.workspace}}/build
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.shared}} \
-A ${{matrix.platform}} \
-DCMAKE_CXX_STANDARD=${{matrix.standard}} \
$GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build
run: |
$threads = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
cmake --build . --config ${{matrix.build_type}} --parallel $threads
- name: Test
working-directory: ${{runner.workspace}}/build
run: ctest -C ${{matrix.build_type}} -V
env:
CTEST_OUTPUT_ON_FAILURE: True

View File

@ -9,6 +9,7 @@ gradle/
gradlew* gradlew*
local.properties local.properties
build/ build/
support/.cxx
bin/ bin/
/_CPack_Packages /_CPack_Packages

View File

@ -1,19 +1,19 @@
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.1...3.18)
# Use newer policies if available, up to most recent tested version of CMake. # Fallback for using newer policies on CMake <3.12.
if(${CMAKE_VERSION} VERSION_LESS 3.11) if(${CMAKE_VERSION} VERSION_LESS 3.12)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.11)
endif() endif()
# Determine if fmt is built as a subproject (using add_subdirectory) # Determine if fmt is built as a subproject (using add_subdirectory)
# or if it is the master project. # or if it is the master project.
set(MASTER_PROJECT OFF) if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(MASTER_PROJECT ON) set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}") message(STATUS "CMake version: ${CMAKE_VERSION}")
endif () endif ()
endif ()
# Joins arguments and places the results in ${result_var}. # Joins arguments and places the results in ${result_var}.
function(join result_var) function(join result_var)
@ -24,6 +24,17 @@ function(join result_var)
set(${result_var} "${result}" PARENT_SCOPE) set(${result_var} "${result}" PARENT_SCOPE)
endfunction() endfunction()
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)
endfunction()
include(CMakeParseArguments) include(CMakeParseArguments)
# Sets a cache variable with a docstring joined from multiple arguments: # Sets a cache variable with a docstring joined from multiple arguments:
@ -46,7 +57,7 @@ endfunction()
# Set the default CMAKE_BUILD_TYPE to Release. # Set the default CMAKE_BUILD_TYPE to Release.
# This should be done before the project command since the latter can set # This should be done before the project command since the latter can set
# CMAKE_BUILD_TYPE itself (it does so for nmake). # CMAKE_BUILD_TYPE itself (it does so for nmake).
if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
@ -55,20 +66,36 @@ endif ()
project(FMT CXX) project(FMT CXX)
include(GNUInstallDirs) include(GNUInstallDirs)
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
"Installation directory for include files, a relative path " "Installation directory for include files, a relative path that "
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
option(FMT_WERROR "Halt the compilation with an error on compiler warnings." option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
OFF) OFF)
# Options that control generation of various targets. # Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT}) option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT}) option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
option(FMT_MODULE "Build a module instead of a traditional library." 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 ON)
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'.")
endif ()
# Get version from core.h # Get version from core.h
file(READ include/fmt/core.h core_h) file(READ include/fmt/core.h core_h)
@ -104,24 +131,36 @@ if (${index} GREATER -1)
endif () endif ()
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") 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")
set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS
hidden default)
endif ()
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN)
set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL
"Whether to add a compile flag to hide symbols of inline functions")
endif ()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
-Wold-style-cast -Wundef -Wold-style-cast -Wundef
-Wredundant-decls -Wwrite-strings -Wpointer-arith -Wredundant-decls -Wwrite-strings -Wpointer-arith
-Wcast-qual -Wformat=2 -Wmissing-include-dirs -Wcast-qual -Wformat=2 -Wmissing-include-dirs
-Wcast-align -Wnon-virtual-dtor -Wcast-align
-Wctor-dtor-privacy -Wdisabled-optimization -Wctor-dtor-privacy -Wdisabled-optimization
-Winvalid-pch -Woverloaded-virtual -Winvalid-pch -Woverloaded-virtual
-Wconversion -Wswitch-enum -Wconversion -Wswitch-enum -Wundef
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow) -Wno-ctor-dtor-privacy -Wno-format-nonliteral)
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
-Wno-dangling-else -Wno-unused-local-typedefs) -Wno-dangling-else -Wno-unused-local-typedefs)
endif () endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
-Wvector-operation-performance -Wsized-deallocation) -Wvector-operation-performance -Wsized-deallocation -Wshadow)
endif () endif ()
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
@ -131,8 +170,9 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif () endif ()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
-Wno-sign-conversion -Wdeprecated -Wweak-vtables) -Wdeprecated -Wweak-vtables -Wshadow
-Wno-gnu-zero-variadic-macro-arguments)
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING) check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
if (HAS_NULLPTR_WARNING) if (HAS_NULLPTR_WARNING)
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
@ -146,7 +186,7 @@ if (MSVC)
set(WERROR_FLAG /WX) set(WERROR_FLAG /WX)
endif () endif ()
if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
# If Microsoft SDK is installed create script run-msbuild.bat that # If Microsoft SDK is installed create script run-msbuild.bat that
# calls SetEnv.cmd to set up build environment and runs msbuild. # calls SetEnv.cmd to set up build environment and runs msbuild.
# It is useful when building Visual Studio projects with the SDK # It is useful when building Visual Studio projects with the SDK
@ -185,9 +225,12 @@ function(add_headers VAR)
endfunction() endfunction()
# Define the fmt library, its includes and the needed defines. # Define the fmt library, its includes and the needed defines.
add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
locale.h os.h ostream.h posix.h printf.h ranges.h) format-inl.h locale.h os.h ostream.h printf.h ranges.h
if (FMT_OS) xchar.h)
if (FMT_MODULE)
set(FMT_SOURCES src/fmt.cc)
elseif (FMT_OS)
set(FMT_SOURCES src/format.cc src/os.cc) set(FMT_SOURCES src/format.cc src/os.cc)
else() else()
set(FMT_SOURCES src/format.cc) set(FMT_SOURCES src/format.cc)
@ -201,8 +244,11 @@ if (HAVE_STRTOD_L)
endif () endif ()
if (MINGW) if (MINGW)
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
if (${FMT_HAS_MBIG_OBJ})
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj") target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
endif() endif()
endif ()
if (FMT_WERROR) if (FMT_WERROR)
target_compile_options(fmt PRIVATE ${WERROR_FLAG}) target_compile_options(fmt PRIVATE ${WERROR_FLAG})
@ -210,6 +256,9 @@ endif ()
if (FMT_PEDANTIC) if (FMT_PEDANTIC)
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
endif () endif ()
if (FMT_MODULE)
enable_module(fmt)
endif ()
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
@ -231,7 +280,8 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
endif () endif ()
if (BUILD_SHARED_LIBS) if (BUILD_SHARED_LIBS)
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND NOT EMSCRIPTEN) if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
NOT EMSCRIPTEN)
# Fix rpmlint warning: # Fix rpmlint warning:
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6. # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
target_link_libraries(fmt -Wl,--as-needed) target_link_libraries(fmt -Wl,--as-needed)
@ -256,20 +306,22 @@ target_include_directories(fmt-header-only INTERFACE
if (FMT_INSTALL) if (FMT_INSTALL)
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
"Installation directory for cmake files, a relative path " "Installation directory for cmake files, a relative path that "
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
"path.")
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
set(targets_export_name fmt-targets) set(targets_export_name fmt-targets)
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
"Installation directory for libraries, a relative path " "Installation directory for libraries, a relative path that "
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
"Installation directory for pkgconfig (.pc) files, a relative path " "Installation directory for pkgconfig (.pc) files, a relative "
"that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
"absolute path.")
# Generate the version, config and target files into the build directory. # Generate the version, config and target files into the build directory.
write_basic_package_version_file( write_basic_package_version_file(
@ -290,6 +342,13 @@ if (FMT_INSTALL)
INSTALL_DESTINATION ${FMT_CMAKE_DIR}) INSTALL_DESTINATION ${FMT_CMAKE_DIR})
set(INSTALL_TARGETS fmt fmt-header-only) set(INSTALL_TARGETS fmt fmt-header-only)
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
# Use a namespace because CMake provides better diagnostics for namespaced # Use a namespace because CMake provides better diagnostics for namespaced
# imported targets. # imported targets.
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt:: export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
@ -302,12 +361,6 @@ if (FMT_INSTALL)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::) NAMESPACE fmt::)
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}> install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
DESTINATION ${FMT_LIB_DIR} OPTIONAL) DESTINATION ${FMT_LIB_DIR} OPTIONAL)
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt") install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
@ -326,11 +379,17 @@ endif ()
# Control fuzzing independent of the unit tests. # Control fuzzing independent of the unit tests.
if (FMT_FUZZ) if (FMT_FUZZ)
add_subdirectory(test/fuzzing) add_subdirectory(test/fuzzing)
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
# mode and make fuzzing practically possible. It is similar to
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
# avoid interfering with fuzzing of projects that use {fmt}.
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
target_compile_definitions(fmt PUBLIC FMT_FUZZ) target_compile_definitions(fmt PUBLIC FMT_FUZZ)
endif () endif ()
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
if (MASTER_PROJECT AND EXISTS ${gitignore}) if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
# Get the list of ignored files from .gitignore. # Get the list of ignored files from .gitignore.
file (STRINGS ${gitignore} lines) file (STRINGS ${gitignore} lines)
list(REMOVE_ITEM lines /doc/html) list(REMOVE_ITEM lines /doc/html)

View File

@ -14,4 +14,7 @@ exceptions:
* snake_case should be used instead of UpperCamelCase for function and type * snake_case should be used instead of UpperCamelCase for function and type
names names
All documentation must adhere to the [Google Developer Documentation Style
Guide](https://developers.google.com/style).
Thanks for contributing! Thanks for contributing!

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +1,69 @@
{fmt} {fmt}
===== =====
.. image:: https://travis-ci.org/fmtlib/fmt.png?branch=master .. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
:target: https://travis-ci.org/fmtlib/fmt :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
.. 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 .. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
:target: https://ci.appveyor.com/project/vitaut/fmt :target: https://ci.appveyor.com/project/vitaut/fmt
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg .. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
:alt: fmt is continuously fuzzed att oss-fuzz :alt: fmt is continuously fuzzed at oss-fuzz
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\ :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\ colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
Summary&q=proj%3Dlibfmt&can=1 Summary&q=proj%3Dfmt&can=1
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg .. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
:alt: Ask questions at StackOverflow with the tag fmt :alt: Ask questions at StackOverflow with the tag fmt
:target: https://stackoverflow.com/questions/tagged/fmt :target: https://stackoverflow.com/questions/tagged/fmt
**{fmt}** is an open-source formatting library for C++. **{fmt}** is an open-source formatting library providing a fast and safe
It can be used as a safe and fast alternative to (s)printf and iostreams. alternative to C stdio and C++ iostreams.
`Documentation <https://fmt.dev/latest/>`__ If you like this project, please consider donating to the BYSOL
Foundation that helps victims of political repressions in Belarus:
https://bysol.org/en/bs/general/.
`Documentation <https://fmt.dev>`__
Q&A: ask questions on `StackOverflow with the tag fmt Q&A: ask questions on `StackOverflow with the tag fmt
<https://stackoverflow.com/questions/tagged/fmt>`_. <https://stackoverflow.com/questions/tagged/fmt>`_.
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
Features Features
-------- --------
* Simple `format API <https://fmt.dev/dev/api.html>`_ with positional arguments * Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
for localization for localization
* Implementation of `C++20 std::format * Implementation of `C++20 std::format
<https://en.cppreference.com/w/cpp/utility/format>`__ <https://en.cppreference.com/w/cpp/utility/format>`__
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one * `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
of Python's
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ `format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
round-trip guarantees
* Safe `printf implementation * Safe `printf implementation
<https://fmt.dev/latest/api.html#printf-formatting>`_ including <https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
the POSIX extension for positional arguments extension for positional arguments
* Extensibility: support for user-defined types * Extensibility: `support for user-defined types
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
* High performance: faster than common standard library implementations of * High performance: faster than common standard library implementations of
`printf <https://en.cppreference.com/w/cpp/io/c/fprintf>`_, ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ and and `Converting a hundred million integers to strings per second
`Converting a hundred million integers to strings per second
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_ <http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
* Small code size both in terms of source code (the minimum configuration * Small code size both in terms of source code with the minimum configuration
consists of just three header files, ``core.h``, ``format.h`` and consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
``format-inl.h``) and compiled code. See `Compile time and code bloat`_ and compiled code; see `Compile time and code bloat`_
* Reliability: the library has an extensive set of `unit tests * Reliability: the library has an extensive set of `tests
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed <https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
* Safety: the library is fully type safe, errors in format strings can be * Safety: the library is fully type safe, errors in format strings can be
reported at compile time, automatic memory management prevents buffer overflow reported at compile time, automatic memory management prevents buffer overflow
errors errors
@ -57,18 +72,17 @@ Features
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_ <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with * `Portability <https://fmt.dev/latest/index.html#portability>`_ with
consistent output across platforms and support for older compilers consistent output across platforms and support for older compilers
* Clean warning-free codebase even on high warning levels * Clean warning-free codebase even on high warning levels such as
(``-Wall -Wextra -pedantic``) ``-Wall -Wextra -pedantic``
* Locale-independence by default * Locale-independence by default
* Support for wide strings
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro * Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
See the `documentation <https://fmt.dev/latest/>`_ for more details. See the `documentation <https://fmt.dev>`_ for more details.
Examples Examples
-------- --------
Print ``Hello, world!`` to ``stdout``: **Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
.. code:: c++ .. code:: c++
@ -78,100 +92,95 @@ Print ``Hello, world!`` to ``stdout``:
fmt::print("Hello, world!\n"); fmt::print("Hello, world!\n");
} }
Format a string: **Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
.. code:: c++ .. code:: c++
std::string s = fmt::format("The answer is {}.", 42); std::string s = fmt::format("The answer is {}.", 42);
// s == "The answer is 42." // s == "The answer is 42."
Format a string using positional arguments: **Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
.. code:: c++ .. code:: c++
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right." // s == "I'd rather be happy than right."
Print a chrono duration: **Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
.. code:: c++ .. code:: c++
#include <fmt/chrono.h> #include <fmt/chrono.h>
int main() { int main() {
using namespace std::chrono_literals; using namespace std::literals::chrono_literals;
fmt::print("Elapsed time: {}", 42ms); fmt::print("Default format: {} {}\n", 42s, 100ms);
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
} }
prints "Elapsed time: 42ms". Output::
Check a format string at compile time: Default format: 42s 100ms
strftime-like format: 03:15:30
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
.. code:: c++ .. code:: c++
// test.cc #include <vector>
#include <fmt/format.h> #include <fmt/ranges.h>
std::string s = format(FMT_STRING("{:d}"), "hello");
gives a compile-time error because ``d`` is an invalid format specifier for a int main() {
string. std::vector<int> v = {1, 2, 3};
fmt::print("{}\n", v);
}
Use {fmt} as a safe portable replacement for ``itoa`` Output::
(`godbolt <https://godbolt.org/g/NXmpU4>`_):
[1, 2, 3]
**Check a format string at compile time**
.. code:: c++ .. code:: c++
fmt::memory_buffer buf; std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number");
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
Format objects of user-defined types via a simple `extension API This gives a compile-time error because ``d`` is an invalid format specifier for
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_: a string.
**Write a file from a single thread**
.. code:: c++ .. code:: c++
#include <fmt/format.h> #include <fmt/os.h>
struct date { int main() {
int year, month, day; auto out = fmt::output_file("guide.txt");
}; out.print("Don't {}", "Panic");
template <>
struct fmt::formatter<date> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const date& d, FormatContext& ctx) {
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
} }
};
std::string s = fmt::format("The date is {}", date{2012, 12, 9}); This can be `5 to 9 times faster than fprintf
// s == "The date is 2012-12-9" <http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
Create your own functions similar to `format **Print with colors and text styles**
<https://fmt.dev/latest/api.html#format>`_ and
`print <https://fmt.dev/latest/api.html#print>`_
which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
.. code:: c++ .. code:: c++
// Prints formatted error message. #include <fmt/color.h>
void vreport_error(const char* format, fmt::format_args args) {
fmt::print("Error: "); int main() {
fmt::vprint(format, args); fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
} "Hello, {}!\n", "world");
template <typename... Args> fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
void report_error(const char* format, const Args & ... args) { fmt::emphasis::underline, "Hello, {}!\n", "мир");
vreport_error(format, fmt::make_format_args(args...)); fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
"Hello, {}!\n", "世界");
} }
report_error("file not found: {}", path); Output on a modern terminal:
Note that ``vreport_error`` is not parameterized on argument types which can .. image:: https://user-images.githubusercontent.com/
improve compile times and reduce code size compared to a fully parameterized 576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
version.
Benchmarks Benchmarks
---------- ----------
@ -198,12 +207,14 @@ or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
further details refer to the `source further details refer to the `source
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_. <https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
{fmt} is up to 10x faster than ``std::ostringstream`` and ``sprintf`` on {fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_) floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
and faster than `double-conversion <https://github.com/google/double-conversion>`_: and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
`ryu <https://github.com/ulfjack/ryu>`_:
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png .. image:: https://user-images.githubusercontent.com/576385/
:target: https://fmt.dev/unknown_mac64_clang10.0.html 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
:target: https://fmt.dev/unknown_mac64_clang12.0.html
Compile time and code bloat Compile time and code bloat
~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -279,33 +290,45 @@ or the bloat test::
$ make bloat-test $ make 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``.
Projects using this library Projects using this library
--------------------------- ---------------------------
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform * `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
real-time strategy game real-time strategy game
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
* `AMPL/MP <https://github.com/ampl/mp>`_: * `AMPL/MP <https://github.com/ampl/mp>`_:
An open-source library for mathematical programming an open-source library for mathematical programming
* `Aseprite <https://github.com/aseprite/aseprite>`_: * `Aseprite <https://github.com/aseprite/aseprite>`_:
Animated sprite editor & pixel art tool animated sprite editor & pixel art tool
* `AvioBook <https://www.aviobook.aero/en>`_: A comprehensive aircraft * `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
operations suite operations suite
* `Celestia <https://celestia.space/>`_: Real-time 3D visualization of space * `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system * `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
* `ccache <https://ccache.dev/>`_: A compiler cache * `Ceph <https://ceph.com/>`_: a scalable distributed storage system
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database management system * `ccache <https://ccache.dev/>`_: a compiler cache
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater * `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
management system
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
vehicle vehicle
* `Drake <https://drake.mit.edu/>`_: A planning, control, and analysis toolbox * `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
for nonlinear dynamical systems (MIT) for nonlinear dynamical systems (MIT)
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus * `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
@ -313,71 +336,82 @@ Projects using this library
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V * `FiveM <https://fivem.net/>`_: a modification framework for GTA V
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
logging library with latency in nanoseconds
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library * `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
* `Grand Mountain Adventure
<https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/>`_:
A beautiful open-world ski & snowboarding game
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_: * `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
Player vs Player Gaming Network with tweaks Player vs Player Gaming Network with tweaks
* `KBEngine <https://kbengine.org/>`_: An open-source MMOG server engine * `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
engine
* `Keypirinha <https://keypirinha.com/>`_: A semantic launcher for Windows * `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): Home theater software * `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
* `Knuth <https://kth.cash/>`_: High-performance Bitcoin full-node * `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
* `Microsoft Verona <https://github.com/microsoft/verona>`_: * `Microsoft Verona <https://github.com/microsoft/verona>`_:
Research programming language for concurrent ownership research programming language for concurrent ownership
* `MongoDB <https://mongodb.com/>`_: Distributed document database * `MongoDB <https://mongodb.com/>`_: distributed document database
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to * `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
generate randomized datasets generate randomized datasets
* `OpenSpace <https://openspaceproject.com/>`_: An open-source * `OpenSpace <https://openspaceproject.com/>`_: an open-source
astrovisualization framework astrovisualization framework
* `PenUltima Online (POL) <https://www.polserver.com/>`_: * `PenUltima Online (POL) <https://www.polserver.com/>`_:
An MMO server, compatible with most Ultima Online clients an MMO server, compatible with most Ultima Online clients
* `PyTorch <https://github.com/pytorch/pytorch>`_: An open-source machine * `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
learning library learning library
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance, * `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
associative database associative database
* `readpe <https://bitbucket.org/sys_dev/readpe>`_: Read Portable Executable * `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: A Redis cluster * `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
navigation, and executing complex multi-line terminal command sequences
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
proxy proxy
* `redpanda <https://vectorized.io/redpanda>`_: A 10x faster Kafka® replacement * `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
for mission critical systems written in C++ for mission critical systems written in C++
* `rpclib <http://rpclib.net/>`_: A modern C++ msgpack-RPC server and client * `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
library library
* `Salesforce Analytics Cloud * `Salesforce Analytics Cloud
<https://www.salesforce.com/analytics-cloud/overview/>`_: <https://www.salesforce.com/analytics-cloud/overview/>`_:
Business intelligence software business intelligence software
* `Scylla <https://www.scylladb.com/>`_: A Cassandra-compatible NoSQL data store * `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
that can handle 1 million transactions per second on a single server that can handle 1 million transactions per second on a single server
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++ * `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
framework for high-performance server applications on modern hardware framework for high-performance server applications on modern hardware
* `spdlog <https://github.com/gabime/spdlog>`_: Super fast C++ logging library * `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
* `Stellar <https://www.stellar.org/>`_: Financial platform * `Stellar <https://www.stellar.org/>`_: financial platform
* `Touch Surgery <https://www.touchsurgery.com/>`_: Surgery simulator * `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source * `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
MMORPG framework MMORPG framework
* `Windows Terminal <https://github.com/microsoft/terminal>`_: The new Windows * `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
Terminal terminal
`More... <https://github.com/search?q=fmtlib&type=Code>`_ `More... <https://github.com/search?q=fmtlib&type=Code>`_
@ -435,7 +469,7 @@ Boost Format
This is a very powerful library which supports both ``printf``-like format This is a very powerful library which supports both ``printf``-like format
strings and positional arguments. Its main drawback is performance. According to strings and positional arguments. Its main drawback is performance. According to
various benchmarks it is much slower than other methods considered here. Boost various, benchmarks it is much slower than other methods considered here. Boost
Format also has excessive build times and severe code bloat issues (see Format also has excessive build times and severe code bloat issues (see
`Benchmarks`_). `Benchmarks`_).

View File

@ -4,10 +4,14 @@ if (NOT DOXYGEN)
return () return ()
endif () endif ()
find_package(PythonInterp QUIET REQUIRED)
add_custom_target(doc add_custom_target(doc
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/build.py ${FMT_VERSION} COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/build.py
${FMT_VERSION}
SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html) SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
DESTINATION share/doc/fmt OPTIONAL DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL
PATTERN ".doctrees" EXCLUDE) PATTERN ".doctrees" EXCLUDE)

View File

@ -6,17 +6,18 @@ API Reference
The {fmt} library API consists of the following parts: The {fmt} library API consists of the following parts:
* :ref:`fmt/core.h <core-api>`: the core API providing argument handling * :ref:`fmt/core.h <core-api>`: the core API providing main formatting functions
facilities and a lightweight subset of formatting functions for ``char``/UTF-8 with compile-time checks and minimal dependencies
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time * :ref:`fmt/format.h <format-api>`: the full format API providing additional
format string checks, wide string, output iterator and user-defined type formatting functions and locale support
support * :ref:`fmt/ranges.h <ranges-api>`: formatting of ranges and tuples
* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
and tuples
* :ref:`fmt/chrono.h <chrono-api>`: date and time formatting * :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
* :ref:`fmt/compile.h <compile-api>`: format string compilation * :ref:`fmt/compile.h <compile-api>`: format string compilation
* :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/ostream.h <ostream-api>`: ``std::ostream`` support
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting * :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
* :ref:`fmt/xchar.h <xchar-api>`: optional ``wchar_t`` support
All functions and types provided by the library reside in namespace ``fmt`` and All functions and types provided by the library reside in namespace ``fmt`` and
macros have prefix ``FMT_``. macros have prefix ``FMT_``.
@ -26,34 +27,56 @@ macros have prefix ``FMT_``.
Core API Core API
======== ========
``fmt/core.h`` defines the core API which provides argument handling facilities ``fmt/core.h`` defines the core API which provides main formatting functions for
and a lightweight subset of formatting functions. In the header-only mode ``char``/UTF-8 with compile-time checks. It has minimal include dependencies for
include ``fmt/format.h`` instead of ``fmt/core.h``. better compile times. This header is only beneficial when using {fmt} as a
library and not in the header-only mode.
The following functions use :ref:`format string syntax <syntax>` The following functions use :ref:`format string syntax <syntax>`
similar to that of Python's `str.format similar to that of Python's `str.format
<http://docs.python.org/3/library/stdtypes.html#str.format>`_. <https://docs.python.org/3/library/stdtypes.html#str.format>`_.
They take *format_str* and *args* as arguments. They take *fmt* and *args* as arguments.
*format_str* is a format string that contains literal text and replacement *fmt* is a format string that contains literal text and replacement
fields surrounded by braces ``{}``. The fields are replaced with formatted fields surrounded by braces ``{}``. The fields are replaced with formatted
arguments in the resulting string. A function taking *format_str* doesn't arguments in the resulting string. A function taking *fmt* doesn't
participate in an overload resolution if the latter is not a string. participate in an overload resolution if the latter is not a string.
*args* is an argument list representing objects to be formatted. *args* is an argument list representing objects to be formatted.
.. _format: .. _format:
.. doxygenfunction:: format(const S&, Args&&...) .. doxygenfunction:: format(format_string<T...> fmt, T&&... args) -> std::string
.. doxygenfunction:: vformat(const S&, basic_format_args<buffer_context<type_identity_t<Char>>>) .. doxygenfunction:: vformat(string_view fmt, format_args args) -> std::string
.. doxygenfunction:: format_to(OutputIt out, format_string<T...> fmt, T&&... args) -> OutputIt
.. doxygenfunction:: format_to_n(OutputIt out, size_t n, format_string<T...> fmt, const T&... args) -> format_to_n_result<OutputIt>
.. doxygenfunction:: formatted_size(format_string<T...> fmt, T&&... args) -> size_t
.. doxygenstruct:: fmt::format_to_n_result
:members:
.. _print: .. _print:
.. doxygenfunction:: print(const S&, Args&&...) .. doxygenfunction:: fmt::print(format_string<T...> fmt, T&&... args)
.. doxygenfunction:: vprint(string_view, format_args) .. doxygenfunction:: vprint(string_view fmt, format_args args)
.. doxygenfunction:: print(std::FILE *, const S&, Args&&...) .. doxygenfunction:: print(std::FILE *f, format_string<T...> fmt, T&&... args)
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args) .. doxygenfunction:: vprint(std::FILE *f, string_view fmt, format_args args)
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.
.. 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.
Named Arguments Named Arguments
--------------- ---------------
@ -65,6 +88,35 @@ Named arguments are not supported in compile-time checks at the moment.
Argument Lists 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_args_checked<Args...>(format, 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_args_checked(const S&, const remove_reference_t<Args>&...)
.. doxygenfunction:: fmt::make_format_args(const Args&...) .. doxygenfunction:: fmt::make_format_args(const Args&...)
.. doxygenclass:: fmt::format_arg_store .. doxygenclass:: fmt::format_arg_store
@ -76,11 +128,16 @@ Argument Lists
.. doxygenclass:: fmt::basic_format_args .. doxygenclass:: fmt::basic_format_args
:members: :members:
.. doxygenstruct:: fmt::format_args .. doxygentypedef:: fmt::format_args
.. doxygenclass:: fmt::basic_format_arg .. doxygenclass:: fmt::basic_format_arg
:members: :members:
.. doxygenclass:: fmt::basic_format_context
:members:
.. doxygentypedef:: fmt::format_context
Compatibility Compatibility
------------- -------------
@ -88,12 +145,11 @@ Compatibility
:members: :members:
.. doxygentypedef:: fmt::string_view .. doxygentypedef:: fmt::string_view
.. doxygentypedef:: fmt::wstring_view
Locale Locale
------ ------
All formatting is locale-independent by default. Use the ``'n'`` format All formatting is locale-independent by default. Use the ``'L'`` format
specifier to insert the appropriate number separator characters from the specifier to insert the appropriate number separator characters from the
locale:: locale::
@ -108,17 +164,10 @@ locale::
Format API Format API
========== ==========
``fmt/format.h`` defines the full format API providing compile-time format ``fmt/format.h`` defines the full format API providing additional formatting
string checks, wide string, output iterator and user-defined type support. functions and locale support.
Compile-time Format String Checks .. _udt:
---------------------------------
Compile-time checks are supported for built-in and string types as well as
user-defined types with ``constexpr`` ``parse`` functions in their ``formatter``
specializations.
.. doxygendefine:: FMT_STRING
Formatting User-defined Types Formatting User-defined Types
----------------------------- -----------------------------
@ -130,14 +179,12 @@ template and implement ``parse`` and ``format`` methods::
struct point { double x, y; }; struct point { double x, y; };
template <> template <> struct fmt::formatter<point> {
struct fmt::formatter<point> {
// Presentation format: 'f' - fixed, 'e' - exponential. // Presentation format: 'f' - fixed, 'e' - exponential.
char presentation = 'f'; char presentation = 'f';
// Parses format specifications of the form ['f' | 'e']. // Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context& ctx) { constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
// auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) // c++11
// [ctx.begin(), ctx.end()) is a character range that contains a part of // [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed, // the format string starting from the format specifications to be parsed,
// e.g. in // e.g. in
@ -164,8 +211,7 @@ template and implement ``parse`` and ``format`` methods::
// Formats the point p using the parsed format specification (presentation) // Formats the point p using the parsed format specification (presentation)
// stored in this formatter. // stored in this formatter.
template <typename FormatContext> template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) { auto format(const point& p, FormatContext& ctx) -> decltype(ctx.out()) {
// auto format(const point &p, FormatContext &ctx) -> decltype(ctx.out()) // c++11
// ctx.out() is an output iterator to write to. // ctx.out() is an output iterator to write to.
return format_to( return format_to(
ctx.out(), ctx.out(),
@ -237,44 +283,36 @@ You can also write a formatter for a hierarchy of classes::
fmt::print("{}", a); // prints "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.
.. doxygenclass:: fmt::basic_format_parse_context .. doxygenclass:: fmt::basic_format_parse_context
:members: :members:
Output Iterator Support
-----------------------
.. doxygenfunction:: fmt::format_to(OutputIt, const S&, Args&&...)
.. doxygenfunction:: fmt::format_to_n(OutputIt, size_t, const S&, const Args&...)
.. doxygenstruct:: fmt::format_to_n_result
:members:
Literal-based API Literal-based API
----------------- -----------------
The following user-defined literals are defined in ``fmt/format.h``. The following user-defined literals are defined in ``fmt/format.h``.
.. doxygenfunction:: operator""_format(const char *, size_t) .. doxygenfunction:: operator""_format(const char *s, size_t n) -> detail::udl_formatter<char>
.. doxygenfunction:: operator""_a(const char *, size_t) .. doxygenfunction:: operator""_a(const char *s, size_t) -> detail::udl_arg<char>
Utilities Utilities
--------- ---------
.. doxygenstruct:: fmt::is_char .. doxygenfunction:: fmt::ptr(T p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::unique_ptr<T> &p) -> const void*
.. doxygenfunction:: fmt::ptr(const std::shared_ptr<T> &p) -> const void*
.. doxygentypedef:: fmt::char_t .. doxygenfunction:: fmt::to_string(const T &value) -> std::string
.. doxygenfunction:: fmt::formatted_size(string_view, const Args&...) .. doxygenfunction:: fmt::to_string_view(const Char *s) -> basic_string_view<Char>
.. doxygenfunction:: fmt::to_string(const T&) .. doxygenfunction:: fmt::join(Range &&range, string_view sep) -> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>>
.. doxygenfunction:: fmt::to_wstring(const T&) .. doxygenfunction:: fmt::join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel>
.. doxygenfunction:: fmt::to_string_view(const Char *)
.. doxygenfunction:: fmt::join(const Range&, string_view)
.. doxygenfunction:: fmt::join(It, Sentinel, string_view)
.. doxygenclass:: fmt::detail::buffer .. doxygenclass:: fmt::detail::buffer
:members: :members:
@ -286,20 +324,14 @@ Utilities
System Errors System Errors
------------- -------------
fmt does not use ``errno`` to communicate errors to the user, but it may call {fmt} does not use ``errno`` to communicate errors to the user, but it may call
system functions which set ``errno``. Users should not make any assumptions about system functions which set ``errno``. Users should not make any assumptions
the value of ``errno`` being preserved by library functions. about the value of ``errno`` being preserved by library functions.
.. doxygenclass:: fmt::system_error .. doxygenfunction:: fmt::system_error
:members:
.. doxygenfunction:: fmt::format_system_error .. doxygenfunction:: fmt::format_system_error
.. doxygenclass:: fmt::windows_error
:members:
.. _formatstrings:
Custom Allocators Custom Allocators
----------------- -----------------
@ -330,10 +362,10 @@ allocator::
return vformat(alloc, format_str, fmt::make_format_args(args...)); return vformat(alloc, format_str, fmt::make_format_args(args...));
} }
The allocator will be used for the output container only. If you are using named The allocator will be used for the output container only. Formatting functions
arguments, the container that stores pointers to them will be allocated using normally don't do any allocations for built-in and string types except for
the default allocator. Also floating-point formatting falls back on ``sprintf`` non-default floating-point formatting that occasionally falls back on
which may do allocations. ``sprintf``.
.. _ranges-api: .. _ranges-api:
@ -365,41 +397,84 @@ Using ``fmt::join``, you can separate tuple elements with a custom separator::
Date and Time Formatting Date and Time Formatting
======================== ========================
The library supports `strftime ``fmt/chrono.h`` provides formatters for
<http://en.cppreference.com/w/cpp/chrono/c/strftime>`_-like date and time
formatting:: * `std::chrono::duration <https://en.cppreference.com/w/cpp/chrono/duration>`_
* `std::chrono::time_point
<https://en.cppreference.com/w/cpp/chrono/time_point>`_
* `std::tm <https://en.cppreference.com/w/cpp/chrono/c/tm>`_
The format syntax is described in :ref:`chrono-specs`.
**Example**::
#include <fmt/chrono.h> #include <fmt/chrono.h>
int main() {
std::time_t t = std::time(nullptr); std::time_t t = std::time(nullptr);
// Prints "The date is 2016-04-29." (with the current date)
// Prints "The date is 2020-11-07." (with the current date):
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
The format string syntax is described in the documentation of using namespace std::literals::chrono_literals;
`strftime <http://en.cppreference.com/w/cpp/chrono/c/strftime>`_.
// Prints "Default format: 42s 100ms":
fmt::print("Default format: {} {}\n", 42s, 100ms);
// Prints "strftime-like format: 03:15:30":
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
}
.. doxygenfunction:: localtime(std::time_t time)
.. doxygenfunction:: gmtime(std::time_t time)
.. _compile-api: .. _compile-api:
Format string compilation Format string compilation
========================= =========================
``fmt/compile.h`` provides format string compilation support. Format strings ``fmt/compile.h`` provides format string compilation support when using
are parsed at compile time and converted into efficient formatting code. This ``FMT_COMPILE``. Format strings are parsed, checked and converted into efficient
supports arguments of built-in and string types as well as user-defined types formatting code at compile-time. This supports arguments of built-in and string
with ``constexpr`` ``parse`` functions in their ``formatter`` specializations. types as well as user-defined types with ``constexpr`` ``parse`` functions in
Format string compilation can generate more binary code compared to the default their ``formatter`` specializations. Format string compilation can generate more
API and is only recommended in places where formatting is a performance binary code compared to the default API and is only recommended in places where
bottleneck. formatting is a performance bottleneck.
.. doxygendefine:: FMT_COMPILE .. doxygendefine:: FMT_COMPILE
.. _color-api:
Terminal color and text style
=============================
``fmt/color.h`` provides support for terminal color and text style output.
.. doxygenfunction:: print(const text_style &ts, const S &format_str, const Args&... args)
.. doxygenfunction:: fg(detail::color_type)
.. doxygenfunction:: bg(detail::color_type)
.. _os-api:
System APIs
===========
.. doxygenclass:: fmt::ostream
:members:
.. doxygenfunction:: fmt::windows_error
:members:
.. _ostream-api: .. _ostream-api:
``std::ostream`` Support ``std::ostream`` Support
======================== ========================
``fmt/ostream.h`` provides ``std::ostream`` support including formatting of ``fmt/ostream.h`` provides ``std::ostream`` support including formatting of
user-defined types that have overloaded ``operator<<``:: user-defined types that have an overloaded insertion operator (``operator<<``)::
#include <fmt/ostream.h> #include <fmt/ostream.h>
@ -416,7 +491,10 @@ user-defined types that have overloaded ``operator<<``::
std::string s = fmt::format("The date is {}", date(2012, 12, 9)); std::string s = fmt::format("The date is {}", date(2012, 12, 9));
// s == "The date is 2012-12-9" // s == "The date is 2012-12-9"
.. doxygenfunction:: print(std::basic_ostream<Char>&, const S&, Args&&...) {fmt} only supports insertion operators that are defined in the same namespaces
as the types they format and can be found with the argument-dependent lookup.
.. doxygenfunction:: print(std::basic_ostream<Char> &os, const S &format_str, Args&&... args)
.. _printf-api: .. _printf-api:
@ -425,18 +503,32 @@ user-defined types that have overloaded ``operator<<``::
The header ``fmt/printf.h`` provides ``printf``-like formatting functionality. The header ``fmt/printf.h`` provides ``printf``-like formatting functionality.
The following functions use `printf format string syntax The following functions use `printf format string syntax
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with <https://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
the POSIX extension for positional arguments. Unlike their standard the POSIX extension for positional arguments. Unlike their standard
counterparts, the ``fmt`` functions are type-safe and throw an exception if an counterparts, the ``fmt`` functions are type-safe and throw an exception if an
argument type doesn't match its format specification. argument type doesn't match its format specification.
.. doxygenfunction:: printf(const S&, const Args&...) .. doxygenfunction:: printf(const S &format_str, const T&... args)
.. doxygenfunction:: fprintf(std::FILE *, const S&, const Args&...) .. doxygenfunction:: fprintf(std::FILE *f, const S &fmt, const T&... args) -> int
.. doxygenfunction:: fprintf(std::basic_ostream<Char>&, const S&, const Args&...) .. doxygenfunction:: sprintf(const S&, const T&...)
.. doxygenfunction:: sprintf(const S&, const Args&...) .. _xchar-api:
``wchar_t`` Support
===================
The optional header ``fmt/wchar_t.h`` provides support for ``wchar_t`` and
exotic character types.
.. doxygenstruct:: fmt::is_char
.. doxygentypedef:: fmt::wstring_view
.. doxygentypedef:: fmt::wformat_context
.. doxygenfunction:: fmt::to_wstring(const T &value)
Compatibility with C++20 ``std::format`` Compatibility with C++20 ``std::format``
======================================== ========================================
@ -447,9 +539,6 @@ differences:
* Names are defined in the ``fmt`` namespace instead of ``std`` to avoid * Names are defined in the ``fmt`` namespace instead of ``std`` to avoid
collisions with standard library implementations. collisions with standard library implementations.
* The ``'L'`` format specifier cannot be combined with presentation specifiers
yet.
* Width calculation doesn't use grapheme clusterization. The latter has been * Width calculation doesn't use grapheme clusterization. The latter has been
implemented in a separate branch but hasn't been integrated yet. implemented in a separate branch but hasn't been integrated yet.
* Chrono formatting doesn't support C++20 date types since they are not provided * Most C++20 chrono types are not supported yet.
by standard library implementations.

View File

@ -1,63 +1,33 @@
#!/usr/bin/env python #!/usr/bin/env python3
# Build the documentation. # Build the documentation.
from __future__ import print_function import errno, os, re, sys
import errno, os, shutil, sys, tempfile from subprocess import check_call, CalledProcessError, Popen, PIPE, STDOUT
from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
from distutils.version import LooseVersion
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'] 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']
def pip_install(package, commit=None, **kwargs): class Pip:
def __init__(self, venv_dir):
self.path = os.path.join(venv_dir, 'bin', 'pip')
def install(self, package, commit=None):
"Install package using pip." "Install package using pip."
min_version = kwargs.get('min_version')
if min_version:
from pkg_resources import get_distribution, DistributionNotFound
try:
installed_version = get_distribution(os.path.basename(package)).version
if LooseVersion(installed_version) >= min_version:
print('{} {} already installed'.format(package, min_version))
return
except DistributionNotFound:
pass
if commit: if commit:
package = 'git+https://github.com/{0}.git@{1}'.format(package, commit) package = 'git+https://github.com/{0}.git@{1}'.format(package, commit)
print('Installing {0}'.format(package)) print('Installing {0}'.format(package))
check_call(['pip', 'install', package]) check_call([self.path, 'install', package])
def create_build_env(dirname='virtualenv'): def create_build_env(venv_dir='virtualenv'):
# Create virtualenv. # Create virtualenv.
if not os.path.exists(dirname): if not os.path.exists(venv_dir):
check_call(['virtualenv', dirname]) check_call(['python3', '-m', 'venv', venv_dir])
import sysconfig # Install Sphinx and Breathe. Require the exact version of Sphinx which is
scripts_dir = os.path.basename(sysconfig.get_path('scripts')) # compatible with Breathe.
activate_this_file = os.path.join(dirname, scripts_dir, 'activate_this.py') pip = Pip(venv_dir)
with open(activate_this_file) as f: pip.install('wheel')
exec(f.read(), dict(__file__=activate_this_file)) pip.install('six')
# Import get_distribution after activating virtualenv to get info about pip.install('sphinx-doc/sphinx', 'v3.3.0')
# the correct packages. pip.install('michaeljones/breathe', 'v4.16.0')
from pkg_resources import get_distribution, DistributionNotFound
# Upgrade pip because installation of sphinx with pip 1.1 available on Travis
# is broken (see #207) and it doesn't support the show command.
pip_version = get_distribution('pip').version
if LooseVersion(pip_version) < LooseVersion('1.5.4'):
print("Updating pip")
check_call(['pip', 'install', '--upgrade', 'pip'])
# Upgrade distribute because installation of sphinx with distribute 0.6.24
# available on Travis is broken (see #207).
try:
distribute_version = get_distribution('distribute').version
if LooseVersion(distribute_version) <= LooseVersion('0.6.24'):
print("Updating distribute")
check_call(['pip', 'install', '--upgrade', 'distribute'])
except DistributionNotFound:
pass
# Install Sphinx and Breathe.
pip_install('sphinx-doc/sphinx', '12b83372ac9316e8cbe86e7fed889296a4cc29ee',
min_version='1.4.1.dev20160531')
pip_install('michaeljones/breathe',
'129222318f7c8f865d2631e7da7b033567e7f56a',
min_version='4.2.0')
def build_docs(version='dev', **kwargs): def build_docs(version='dev', **kwargs):
doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__))) doc_dir = kwargs.get('doc_dir', os.path.dirname(os.path.realpath(__file__)))
@ -66,16 +36,17 @@ def build_docs(version='dev', **kwargs):
'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt')) 'include_dir', os.path.join(os.path.dirname(doc_dir), 'include', 'fmt'))
# Build docs. # Build docs.
cmd = ['doxygen', '-'] cmd = ['doxygen', '-']
p = Popen(cmd, stdin=PIPE) p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
doxyxml_dir = os.path.join(work_dir, 'doxyxml') doxyxml_dir = os.path.join(work_dir, 'doxyxml')
p.communicate(input=r''' out, _ = p.communicate(input=r'''
PROJECT_NAME = fmt PROJECT_NAME = fmt
GENERATE_LATEX = NO GENERATE_LATEX = NO
GENERATE_MAN = NO GENERATE_MAN = NO
GENERATE_RTF = NO GENERATE_RTF = NO
CASE_SENSE_NAMES = NO CASE_SENSE_NAMES = NO
INPUT = {0}/core.h {0}/compile.h {0}/format.h {0}/os.h \ INPUT = {0}/chrono.h {0}/color.h {0}/core.h {0}/compile.h \
{0}/ostream.h {0}/printf.h {0}/time.h {0}/format.h {0}/os.h {0}/ostream.h {0}/printf.h \
{0}/xchar.h
QUIET = YES QUIET = YES
JAVADOC_AUTOBRIEF = YES JAVADOC_AUTOBRIEF = YES
AUTOLINK_SUPPORT = NO AUTOLINK_SUPPORT = NO
@ -86,6 +57,7 @@ def build_docs(version='dev', **kwargs):
ALIASES += "endrst=\endverbatim" ALIASES += "endrst=\endverbatim"
MACRO_EXPANSION = YES MACRO_EXPANSION = YES
PREDEFINED = _WIN32=1 \ PREDEFINED = _WIN32=1 \
__linux__=1 \
FMT_USE_VARIADIC_TEMPLATES=1 \ FMT_USE_VARIADIC_TEMPLATES=1 \
FMT_USE_RVALUE_REFERENCES=1 \ FMT_USE_RVALUE_REFERENCES=1 \
FMT_USE_USER_DEFINED_LITERALS=1 \ FMT_USE_USER_DEFINED_LITERALS=1 \
@ -94,20 +66,37 @@ def build_docs(version='dev', **kwargs):
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_BEGIN_NAMESPACE=namespace fmt {{" \
"FMT_END_NAMESPACE=}}" \ "FMT_END_NAMESPACE=}}" \
"FMT_STRING_ALIAS=1" \ "FMT_STRING_ALIAS=1" \
"FMT_ENABLE_IF(B)=" "FMT_DOC=1"
EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str EXCLUDE_SYMBOLS = fmt::formatter fmt::printf_formatter fmt::arg_join \
fmt::basic_format_arg::handle
'''.format(include_dir, doxyxml_dir).encode('UTF-8')) '''.format(include_dir, doxyxml_dir).encode('UTF-8'))
out = out.decode('utf-8')
internal_symbols = [
'fmt::detail::.*',
'basic_data<>',
'fmt::type_identity',
'fmt::dynamic_formatter'
]
noisy_warnings = [
'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \
') is not documented.',
'warning: Internal inconsistency: .* does not belong to any container!'
]
for w in noisy_warnings:
out = re.sub('.*' + w + '\n', '', out)
print(out)
if p.returncode != 0: if p.returncode != 0:
raise CalledProcessError(p.returncode, cmd) raise CalledProcessError(p.returncode, cmd)
html_dir = os.path.join(work_dir, 'html') html_dir = os.path.join(work_dir, 'html')
main_versions = reversed(versions[-3:]) main_versions = reversed(versions[-3:])
check_call(['sphinx-build', check_call([os.path.join(work_dir, 'virtualenv', 'bin', 'sphinx-build'),
'-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir), '-Dbreathe_projects.format=' + os.path.abspath(doxyxml_dir),
'-Dversion=' + version, '-Drelease=' + version, '-Dversion=' + version, '-Drelease=' + version,
'-Aversion=' + version, '-Aversions=' + ','.join(main_versions), '-Aversion=' + version, '-Aversions=' + ','.join(main_versions),
'-b', 'html', doc_dir, html_dir]) '-b', 'html', doc_dir, html_dir])
try: try:
check_call(['lessc', '--clean-css', check_call(['lessc', '--verbose', '--clean-css',
'--include-path=' + os.path.join(doc_dir, 'bootstrap'), '--include-path=' + os.path.join(doc_dir, 'bootstrap'),
os.path.join(doc_dir, 'fmt.less'), os.path.join(doc_dir, 'fmt.less'),
os.path.join(html_dir, '_static', 'fmt.css')]) os.path.join(html_dir, '_static', 'fmt.css')])

View File

@ -56,6 +56,11 @@ div.sphinxsidebar {
padding: 0; padding: 0;
} }
// Override center alignment in tables.
td {
text-align: left;
}
p.rubric { p.rubric {
margin-top: 10px; margin-top: 10px;
} }

View File

@ -23,24 +23,26 @@ Format API
The format API is similar in spirit to the C ``printf`` family of function but The format API is similar in spirit to the C ``printf`` family of function but
is safer, simpler and several times `faster is safer, simpler and several times `faster
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_ <https://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
than common standard library implementations. than common standard library implementations.
The `format string syntax <syntax.html>`_ is similar to the one used by The `format string syntax <syntax.html>`_ is similar to the one used by
`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_ in
Python: Python:
.. code:: c++ .. code:: c++
fmt::format("The answer is {}.", 42); std::string s = fmt::format("The answer is {}.", 42);
The ``fmt::format`` function returns a string "The answer is 42.". You can use The ``fmt::format`` function returns a string "The answer is 42.". You can use
``fmt::memory_buffer`` to avoid constructing ``std::string``: ``fmt::memory_buffer`` to avoid constructing ``std::string``:
.. code:: c++ .. code:: c++
fmt::memory_buffer out; auto out = fmt::memory_buffer();
format_to(out, "For a moment, {} happened.", "nothing"); format_to(std::back_inserter(out),
out.data(); // returns a pointer to the formatted data "For a moment, {} happened.", "nothing");
auto data = out.data(); // pointer to the formatted data
auto size = out.size(); // size of the formatted data
The ``fmt::print`` function performs formatting and writes the result to a stream: The ``fmt::print`` function performs formatting and writes the result to a stream:
@ -48,21 +50,19 @@ The ``fmt::print`` function performs formatting and writes the result to a strea
fmt::print(stderr, "System error code = {}\n", errno); fmt::print(stderr, "System error code = {}\n", errno);
The file argument can be omitted in which case the function prints to If you omit the file argument the function will print to ``stdout``:
``stdout``:
.. code:: c++ .. code:: c++
fmt::print("Don't {}\n", "panic"); fmt::print("Don't {}\n", "panic");
The Format API also supports positional arguments useful for localization: The format API also supports positional arguments useful for localization:
.. code:: c++ .. code:: c++
fmt::print("I'd rather be {1} than {0}.", "right", "happy"); fmt::print("I'd rather be {1} than {0}.", "right", "happy");
Named arguments can be created with ``fmt::arg``. This makes it easier to track You can pass named arguments with ``fmt::arg``:
what goes where when multiple arguments are being formatted:
.. code:: c++ .. code:: c++
@ -91,16 +91,17 @@ time. For example, the code
fmt::format("The answer is {:d}", "forty-two"); fmt::format("The answer is {:d}", "forty-two");
throws a ``format_error`` exception with description "unknown format code 'd' for throws the ``format_error`` exception because the argument ``"forty-two"`` is a
string", because the argument ``"forty-two"`` is a string while the format code string while the format code ``d`` only applies to integers.
``d`` only applies to integers, while
The code
.. code:: c++ .. code:: c++
format(FMT_STRING("The answer is {:d}"), "forty-two"); format(FMT_STRING("The answer is {:d}"), "forty-two");
reports a compile-time error for the same reason on compilers that support reports a compile-time error on compilers that support relaxed ``constexpr``.
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details. See `here <api.html#c.fmt>`_ for details.
The following code The following code
@ -109,21 +110,15 @@ The following code
fmt::format("Cyrillic letter {}", L'\x42e'); fmt::format("Cyrillic letter {}", L'\x42e');
produces a compile-time error because wide character ``L'\x42e'`` cannot be produces a compile-time error because wide character ``L'\x42e'`` cannot be
formatted into a narrow string. You can use a wide format string instead: formatted into a narrow string. For comparison, writing a wide character to
``std::ostream`` results in its numeric value being written to the stream
.. code:: c++ (i.e. 1070 instead of letter 'ю' which is represented by ``L'\x42e'`` if we
use Unicode) which is rarely desirable.
fmt::format(L"Cyrillic letter {}", L'\x42e');
For comparison, writing a wide character to ``std::ostream`` results in
its numeric value being written to the stream (i.e. 1070 instead of letter 'ю'
which is represented by ``L'\x42e'`` if we use Unicode) which is rarely what is
needed.
Compact Binary Code Compact Binary Code
------------------- -------------------
The library is designed to produce compact per-call compiled code. For example The library produces compact per-call compiled code. For example
(`godbolt <https://godbolt.org/g/TZU4KF>`_), (`godbolt <https://godbolt.org/g/TZU4KF>`_),
.. code:: c++ .. code:: c++
@ -144,8 +139,8 @@ compiles to just
mov rcx, rsp mov rcx, rsp
mov edi, offset .L.str mov edi, offset .L.str
mov esi, 17 mov esi, 17
mov edx, 2 mov edx, 1
call fmt::v5::vprint(fmt::v5::basic_string_view<char>, fmt::v5::format_args) call fmt::v7::vprint(fmt::v7::basic_string_view<char>, fmt::v7::format_args)
xor eax, eax xor eax, eax
add rsp, 24 add rsp, 24
ret ret
@ -167,20 +162,19 @@ The library is highly portable and relies only on a small set of C++11 features:
* deleted functions * deleted functions
* alias templates * alias templates
These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older These are available in GCC 4.8, Clang 3.4, MSVC 19.0 (2015) and more recent
compilers use {fmt} `version 4.x compiler version. For older compilers use {fmt} `version 4.x
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be <https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which is maintained and
maintained and only requires C++98. only requires C++98.
The output of all formatting functions is consistent across platforms. In The output of all formatting functions is consistent across platforms.
particular, formatting a floating-point infinity always gives ``inf`` while the For example,
output of ``printf`` is platform-dependent. For example,
.. code:: .. code::
fmt::print("{}", std::numeric_limits<double>::infinity()); fmt::print("{}", std::numeric_limits<double>::infinity());
always prints ``inf``. always prints ``inf`` while the output of ``printf`` is platform-dependent.
.. _ease-of-use: .. _ease-of-use:
@ -192,11 +186,13 @@ just three header files and no external dependencies.
A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
using the library both in open-source and commercial projects. using the library both in open-source and commercial projects.
`Learn more... <contents.html>`_
.. raw:: html .. raw:: html
<a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a> <a class="btn btn-success" href="https://github.com/fmtlib/fmt">GitHub Repository</a>
<div class="section footer"> <div class="section footer">
<iframe src="http://ghbtns.com/github-btn.html?user=fmtlib&amp;repo=fmt&amp;type=watch&amp;count=true" <iframe src="https://ghbtns.com/github-btn.html?user=fmtlib&amp;repo=fmt&amp;type=watch&amp;count=true"
class="github-btn" width="100" height="20"></iframe> class="github-btn" width="100" height="20"></iframe>
</div> </div>

View File

@ -16,7 +16,7 @@ literal text, it can be escaped by doubling: ``{{`` and ``}}``.
The grammar for a replacement field is as follows: The grammar for a replacement field is as follows:
.. productionlist:: sf .. productionlist:: sf
replacement_field: "{" [`arg_id`] [":" `format_spec`] "}" replacement_field: "{" [`arg_id`] [":" (`format_spec` | `chrono_format_spec`)] "}"
arg_id: `integer` | `identifier` arg_id: `integer` | `identifier`
integer: `digit`+ integer: `digit`+
digit: "0"..."9" digit: "0"..."9"
@ -27,8 +27,8 @@ The grammar for a replacement field is as follows:
In less formal terms, the replacement field can start with an *arg_id* In less formal terms, the replacement field can start with an *arg_id*
that specifies the argument whose value is to be formatted and inserted into that specifies the argument whose value is to be formatted and inserted into
the output instead of the replacement field. the output instead of the replacement field.
The *arg_id* is optionally followed by a *format_spec*, which is preceded The *arg_id* is optionally followed by a *format_spec*, which is preceded by a
by a colon ``':'``. These specify a non-default format for the replacement value. colon ``':'``. These specify a non-default format for the replacement value.
See also the :ref:`formatspec` section. See also the :ref:`formatspec` section.
@ -75,14 +75,14 @@ although some of the formatting options are only supported by the numeric types.
The general form of a *standard format specifier* is: The general form of a *standard format specifier* is:
.. productionlist:: sf .. productionlist:: sf
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`] format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`]["L"][`type`]
fill: <a character other than '{' or '}'> fill: <a character other than '{' or '}'>
align: "<" | ">" | "^" align: "<" | ">" | "^"
sign: "+" | "-" | " " sign: "+" | "-" | " "
width: `integer` | "{" [`arg_id`] "}" width: `integer` | "{" [`arg_id`] "}"
precision: `integer` | "{" [`arg_id`] "}" precision: `integer` | "{" [`arg_id`] "}"
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s" type: "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" |
int_type: "b" | "B" | "d" | "o" | "x" | "X" : "o" | "p" | "s" | "x" | "X"
The *fill* character can be any Unicode code point other than ``'{'`` or The *fill* character can be any Unicode code point other than ``'{'`` or
``'}'``. The presence of a fill character is signaled by the character following ``'}'``. The presence of a fill character is signaled by the character following
@ -163,6 +163,9 @@ indicates the maximum field size - in other words, how many characters will be
used from the field content. The *precision* is not allowed for integer, used from the field content. The *precision* is not allowed for integer,
character, Boolean, and pointer values. character, Boolean, and pointer values.
The ``'L'`` option uses the current locale setting to insert the appropriate
number separator characters. This option is only valid for numeric types.
Finally, the *type* determines how the data should be presented. Finally, the *type* determines how the data should be presented.
The available string presentation types are: The available string presentation types are:
@ -200,6 +203,8 @@ The available integer presentation types are:
| | ``'#'`` option with this type adds the prefix ``"0B"`` | | | ``'#'`` option with this type adds the prefix ``"0B"`` |
| | to the output value. | | | to the output value. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'c'`` | Character format. Outputs the number as a character. |
+---------+----------------------------------------------------------+
| ``'d'`` | Decimal integer. Outputs the number in base 10. | | ``'d'`` | Decimal integer. Outputs the number in base 10. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'o'`` | Octal format. Outputs the number in base 8. | | ``'o'`` | Octal format. Outputs the number in base 8. |
@ -214,10 +219,6 @@ The available integer presentation types are:
| | ``'#'`` option with this type adds the prefix ``"0X"`` | | | ``'#'`` option with this type adds the prefix ``"0X"`` |
| | to the output value. | | | to the output value. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'L'`` | Locale-specific format. This is the same as ``'d'``, |
| | except that it uses the current locale setting to insert |
| | the appropriate number separator characters. |
+---------+----------------------------------------------------------+
| none | The same as ``'d'``. | | none | The same as ``'d'``. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
@ -261,14 +262,8 @@ The available presentation types for floating-point values are:
| | ``'E'`` if the number gets too large. The | | | ``'E'`` if the number gets too large. The |
| | representations of infinity and NaN are uppercased, too. | | | representations of infinity and NaN are uppercased, too. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
| ``'L'`` | Locale-specific format. This is the same as ``'g'``, | | none | Similar to ``'g'``, except that the default precision is |
| | except that it uses the current locale setting to insert | | | as high as needed to represent the particular value. |
| | the appropriate number separator characters. |
+---------+----------------------------------------------------------+
| none | Similar to ``'g'``, except that fixed-point notation, |
| | when used, has at least one digit past the decimal |
| | point. The default precision is as high as needed to |
| | represent the particular value. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
.. ifconfig:: False .. ifconfig:: False
@ -303,6 +298,59 @@ The available presentation types for pointers are:
| none | The same as ``'p'``. | | none | The same as ``'p'``. |
+---------+----------------------------------------------------------+ +---------+----------------------------------------------------------+
.. _chrono-specs:
Chrono Format Specifications
============================
Format specifications for chrono types have the following syntax:
.. productionlist:: sf
chrono_format_spec: [[`fill`]`align`][`width`]["." `precision`][`chrono_specs`]
chrono_specs: [`chrono_specs`] `conversion_spec` | `chrono_specs` `literal_char`
conversion_spec: "%" [`modifier`] `chrono_type`
literal_char: <a character other than '{', '}' or '%'>
modifier: "E" | "O"
chrono_type: "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" | "F" |
: "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" | "n" | "p" |
: "q" | "Q" | "r" | "R" | "S" | "t" | "T" | "u" | "U" | "V" |
: "w" | "W" | "x" | "X" | "y" | "Y" | "z" | "Z" | "%"
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:
+---------+--------------------------------------------------------------------+
| Type | Meaning |
+=========+====================================================================+
| ``'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. |
+---------+--------------------------------------------------------------------+
| ``'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. |
+---------+--------------------------------------------------------------------+
| ``'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 |
| | decimal floating-point number with a fixed format and a precision |
| | matching that of the precision of the input (or to a microseconds |
| | precision if the conversion to floating-point decimal seconds |
| | cannot be made within 18 fractional digits). The character for the |
| | decimal point is localized according to the locale. The modified |
| | command ``%OS`` produces the locale's alternative representation. |
+---------+--------------------------------------------------------------------+
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.
``std::tm`` uses the system's `strftime
<https://en.cppreference.com/w/cpp/chrono/c/strftime>`_ so refer to its
documentation for details on supported conversion specifiers.
.. _formatexamples: .. _formatexamples:
Format Examples Format Examples
@ -391,7 +439,7 @@ Using type-specific formatting::
auto t = tm(); auto t = tm();
t.tm_year = 2010 - 1900; t.tm_year = 2010 - 1900;
t.tm_mon = 6; t.tm_mon = 7;
t.tm_mday = 4; t.tm_mday = 4;
t.tm_hour = 12; t.tm_hour = 12;
t.tm_min = 15; t.tm_min = 15;

View File

@ -15,7 +15,7 @@ Building the Library
The included `CMake build script`__ can be used to build the fmt The included `CMake build script`__ can be used to build the fmt
library on a wide range of platforms. CMake is freely available for library on a wide range of platforms. CMake is freely available for
download from http://www.cmake.org/download/. download from https://www.cmake.org/download/.
__ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt __ https://github.com/fmtlib/fmt/blob/master/CMakeLists.txt
@ -50,7 +50,15 @@ To build a `shared library`__ set the ``BUILD_SHARED_LIBS`` CMake variable to
cmake -DBUILD_SHARED_LIBS=TRUE ... cmake -DBUILD_SHARED_LIBS=TRUE ...
__ http://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries __ https://en.wikipedia.org/wiki/Library_%28computing%29#Shared_libraries
To build a `static library` with position independent code (required if the main
consumer of the fmt library is a shared library i.e. a Python extension) set the
``CMAKE_POSITION_INDEPENDENT_CODE`` CMake variable to ``TRUE``::
cmake -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE ...
Installing the Library Installing the Library
====================== ======================
@ -83,6 +91,49 @@ Setting up your target to use a header-only version of ``fmt`` is equally easy::
target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only) target_link_libraries(<your-target> PRIVATE fmt::fmt-header-only)
Usage with build2
=================
You can use `build2 <https://build2.org>`_, a dependency manager and a
build-system combined, to use ``fmt``.
Currently this package is available in these package repositories:
- **https://cppget.org/fmt/** for released and published versions.
- `The git repository with the sources of the build2 package of fmt <https://github.com/build2-packaging/fmt.git>`_
for unreleased or custom revisions of ``fmt``.
**Usage:**
- ``build2`` package name: ``fmt``
- Library target name : ``lib{fmt}``
For example, to make your ``build2`` project depend on ``fmt``:
- Add one of the repositories to your configurations, or in your
``repositories.manifest``, if not already there::
:
role: prerequisite
location: https://pkg.cppget.org/1/stable
- Add this package as a dependency to your ``./manifest`` file
(example for ``v7.0.x``)::
depends: fmt ~7.0.0
- Import the target and use it as a prerequisite to your own target
using `fmt` in the appropriate ``buildfile``::
import fmt = fmt%lib{fmt}
lib{mylib} : cxx{**} ... $fmt
Then build your project as usual with `b` or `bdep update`.
For ``build2`` newcomers or to get more details and use cases, you can read the
``build2``
`toolchain introduction <https://build2.org/build2-toolchain/doc/build2-toolchain-intro.xhtml>`_.
Building the Documentation Building the Documentation
========================== ==========================
@ -130,6 +181,18 @@ The fmt port in vcpkg is kept up to date by Microsoft team members and community
contributors. If the version is out of date, please `create an issue or pull contributors. If the version is out of date, please `create an issue or pull
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository. request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
LHelper
=======
You can download and install fmt using
`lhelper <https://github.com/franko/lhelper>`__ dependency manager::
lhelper activate <some-environment>
lhelper install fmt
All the recipes for lhelper are kept in the
`lhelper's recipe <https://github.com/franko/lhelper-recipes>`__ repository.
Android NDK Android NDK
=========== ===========
@ -139,11 +202,11 @@ For an example of using fmt with Android NDK, see the
`android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_ `android-ndk-example <https://github.com/fmtlib/android-ndk-example>`_
repository. repository.
__ https://github.com/fmtlib/fmt/blob/master/Android.mk __ https://github.com/fmtlib/fmt/blob/master/support/Android.mk
Homebrew Homebrew
======== ========
fmt can be installed on OS X using `Homebrew <http://brew.sh/>`_:: fmt can be installed on OS X using `Homebrew <https://brew.sh/>`_::
brew install fmt brew install fmt

View File

@ -0,0 +1,232 @@
// Formatting library for C++ - dynamic format arguments
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_ARGS_H_
#define FMT_ARGS_H_
#include <functional> // std::reference_wrapper
#include <memory> // std::unique_ptr
#include <vector>
#include "core.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {};
template <typename T>
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
template <typename T> const T& unwrap(const T& v) { return v; }
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
return static_cast<const T&>(v);
}
class dynamic_arg_list {
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So storage_node_base is made a fake template.
template <typename = void> struct node {
virtual ~node() = default;
std::unique_ptr<node<>> next;
};
template <typename T> struct typed_node : node<> {
T value;
template <typename Arg>
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
template <typename Char>
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
: value(arg.data(), arg.size()) {}
};
std::unique_ptr<node<>> head_;
public:
template <typename T, typename Arg> const T& push(const Arg& arg) {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value;
}
};
} // namespace detail
/**
\rst
A dynamic version of `fmt::format_arg_store`.
It's equipped with a storage to potentially temporary objects which lifetimes
could be shorter than the format arguments object.
It can be implicitly converted into `~fmt::basic_format_args` for passing
into type-erased formatting functions such as `~fmt::vformat`.
\endrst
*/
template <typename Context>
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private:
using char_type = typename Context::char_type;
template <typename T> struct need_copy {
static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value;
enum {
value = !(detail::is_reference_wrapper<T>::value ||
std::is_same<T, basic_string_view<char_type>>::value ||
std::is_same<T, detail::std_string_view<char_type>>::value ||
(mapped_type != detail::type::cstring_type &&
mapped_type != detail::type::string_type &&
mapped_type != detail::type::custom_type))
};
};
template <typename T>
using stored_type = conditional_t<detail::is_string<T>::value &&
!has_formatter<T, Context>::value &&
!detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>;
// Storage of basic_format_arg must be contiguous.
std::vector<basic_format_arg<Context>> data_;
std::vector<detail::named_arg_info<char_type>> named_info_;
// Storage of arguments not fitting into basic_format_arg must grow
// without relocation because items in data_ refer to it.
detail::dynamic_arg_list dynamic_args_;
friend class basic_format_args<Context>;
unsigned long long get_types() const {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
const basic_format_arg<Context>* data() const {
return named_info_.empty() ? data_.data() : data_.data() + 1;
}
template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg));
}
template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) {
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
data_.insert(data_.begin(), {zero_ptr, 0});
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back();
};
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
guard.release();
}
public:
/**
\rst
Adds an argument into the dynamic store for later passing to a formatting
function.
Note that custom types and string types (but not string views) are copied
into the store dynamically allocating memory if necessary.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(42);
store.push_back("abc");
store.push_back(1.5f);
std::string result = fmt::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
emplace_arg(detail::unwrap(arg));
}
/**
\rst
Adds a reference to the argument into the dynamic store for later passing to
a formatting function.
**Example**::
fmt::dynamic_format_arg_store<fmt::format_context> store;
char band[] = "Rolling Stones";
store.push_back(std::cref(band));
band[9] = 'c'; // Changing str affects the output.
std::string result = fmt::vformat("{}", store);
// result == "Rolling Scones"
\endrst
*/
template <typename T> void push_back(std::reference_wrapper<T> arg) {
static_assert(
need_copy<T>::value,
"objects of built-in types and string views are always copied");
emplace_arg(arg.get());
}
/**
Adds named argument into the dynamic store for later passing to a formatting
function. ``std::reference_wrapper`` is supported to avoid copying of the
argument. The name is always copied into the store.
*/
template <typename T>
void push_back(const detail::named_arg<char_type, T>& arg) {
const char_type* arg_name =
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
} else {
emplace_arg(fmt::arg(arg_name, arg.value));
}
}
/** Erase all elements from the store */
void clear() {
data_.clear();
named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list();
}
/**
\rst
Reserves space to store at least *new_cap* arguments including
*new_cap_named* named arguments.
\endrst
*/
void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments");
data_.reserve(new_cap);
named_info_.reserve(new_cap_named);
}
};
FMT_END_NAMESPACE
#endif // FMT_ARGS_H_

View File

@ -8,13 +8,13 @@
#ifndef FMT_CHRONO_H_ #ifndef FMT_CHRONO_H_
#define FMT_CHRONO_H_ #define FMT_CHRONO_H_
#include <algorithm>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <locale> #include <locale>
#include <sstream> #include <sstream>
#include "format.h" #include "format.h"
#include "locale.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
static_assert(F::is_integer, "From must be integral"); static_assert(F::is_integer, "From must be integral");
static_assert(T::is_integer, "To must be integral"); static_assert(T::is_integer, "To must be integral");
if (F::is_signed && !T::is_signed) { if (detail::const_check(F::is_signed && !T::is_signed)) {
// From may be negative, not allowed! // From may be negative, not allowed!
if (fmt::detail::is_negative(from)) { if (fmt::detail::is_negative(from)) {
ec = 1; ec = 1;
return {}; return {};
} }
// From is positive. Can it always fit in To? // From is positive. Can it always fit in To?
if (F::digits <= T::digits) { if (F::digits > T::digits &&
// yes, From always fits in To. from > static_cast<From>(detail::max_value<To>())) {
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
ec = 1; ec = 1;
return {}; return {};
} }
} }
}
if (!F::is_signed && T::is_signed) { if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
// can from be held in To? from > static_cast<From>(detail::max_value<To>())) {
if (F::digits < T::digits) {
// yes, From always fits in To.
} else {
// from may not fit in To, we have to do a dynamic check
if (from > static_cast<From>((T::max)())) {
// outside range.
ec = 1; ec = 1;
return {}; return {};
} }
return static_cast<To>(from); // Lossless conversion.
} }
}
// reaching here means all is ok for lossless conversion.
return static_cast<To>(from);
} // function
template <typename To, typename From, template <typename To, typename From,
FMT_ENABLE_IF(std::is_same<From, To>::value)> FMT_ENABLE_IF(std::is_same<From, To>::value)>
@ -190,11 +174,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
// safe conversion to IntermediateRep // safe conversion to IntermediateRep
IntermediateRep count = IntermediateRep count =
lossless_integral_conversion<IntermediateRep>(from.count(), ec); lossless_integral_conversion<IntermediateRep>(from.count(), ec);
if (ec) { if (ec) return {};
return {};
}
// multiply with Factor::num without overflow or underflow // multiply with Factor::num without overflow or underflow
if (Factor::num != 1) { if (detail::const_check(Factor::num != 1)) {
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num; const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
if (count > max1) { if (count > max1) {
ec = 1; ec = 1;
@ -209,17 +191,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
count *= Factor::num; count *= Factor::num;
} }
// this can't go wrong, right? den>0 is checked earlier. if (detail::const_check(Factor::den != 1)) count /= Factor::den;
if (Factor::den != 1) { auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
count /= Factor::den; return ec ? To() : To(tocount);
}
// convert to the to type, safely
using ToRep = typename To::rep;
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
if (ec) {
return {};
}
return To{tocount};
} }
/** /**
@ -308,13 +282,89 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
#define FMT_NOMACRO #define FMT_NOMACRO
namespace detail { namespace detail {
template <typename T = void> struct null {};
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
inline null<> localtime_s(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); }
inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); }
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
char modifier) -> std::string {
auto&& os = std::ostringstream();
os.imbue(loc);
using iterator = std::ostreambuf_iterator<char>;
const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
auto end = facet.put(os, os, ' ', &time, format, modifier);
if (end.failed()) FMT_THROW(format_error("failed to format time"));
auto str = os.str();
if (!detail::is_utf8() || loc == std::locale::classic()) return str;
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
// gcc-4.
#if FMT_MSC_VER != 0 || \
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
// and newer.
using code_unit = wchar_t;
#else
using code_unit = char32_t;
#endif
auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
auto mb = std::mbstate_t();
const char* from_next = nullptr;
code_unit* to_next = nullptr;
constexpr size_t buf_size = 32;
code_unit buf[buf_size] = {};
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
buf + buf_size, to_next);
if (result != std::codecvt_base::ok)
FMT_THROW(format_error("failed to format time"));
str.clear();
for (code_unit* p = buf; p != to_next; ++p) {
uint32_t c = static_cast<uint32_t>(*p);
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
// surrogate pair
++p;
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
FMT_THROW(format_error("failed to format time"));
}
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
}
if (c < 0x80) {
str.push_back(static_cast<char>(c));
} else if (c < 0x800) {
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if (c >= 0x10000 && c <= 0x10ffff) {
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else {
FMT_THROW(format_error("failed to format time"));
}
}
return str;
}
template <typename OutputIt>
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
char format, char modifier = 0) -> OutputIt {
auto str = do_write(time, loc, format, modifier);
return std::copy(str.begin(), str.end(), out);
}
} // namespace detail } // namespace detail
// Thread-safe replacement for std::localtime FMT_MODULE_EXPORT_BEGIN
/**
Converts given time since epoch as ``std::time_t`` value into calendar time,
expressed in local time. Unlike ``std::localtime``, this function is
thread-safe on most platforms.
*/
inline std::tm localtime(std::time_t time) { inline std::tm localtime(std::time_t time) {
struct dispatcher { struct dispatcher {
std::time_t time_; std::time_t time_;
@ -351,7 +401,16 @@ inline std::tm localtime(std::time_t time) {
return lt.tm_; return lt.tm_;
} }
// Thread-safe replacement for std::gmtime inline std::tm localtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return localtime(std::chrono::system_clock::to_time_t(time_point));
}
/**
Converts given time since epoch as ``std::time_t`` value into calendar time,
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
function is thread-safe on most platforms.
*/
inline std::tm gmtime(std::time_t time) { inline std::tm gmtime(std::time_t time) {
struct dispatcher { struct dispatcher {
std::time_t time_; std::time_t time_;
@ -387,33 +446,89 @@ inline std::tm gmtime(std::time_t time) {
return gt.tm_; return gt.tm_;
} }
namespace detail { inline std::tm gmtime(
std::chrono::time_point<std::chrono::system_clock> time_point) {
return gmtime(std::chrono::system_clock::to_time_t(time_point));
}
FMT_BEGIN_DETAIL_NAMESPACE
inline size_t strftime(char* str, size_t count, const char* format, inline size_t strftime(char* str, size_t count, const char* format,
const std::tm* time) { const std::tm* time) {
return std::strftime(str, count, format, time); // Assign to a pointer to suppress GCCs -Wformat-nonliteral
// First assign the nullptr to suppress -Wsuggest-attribute=format
std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
nullptr;
strftime = std::strftime;
return strftime(str, count, format, time);
} }
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
const std::tm* time) { const std::tm* time) {
return std::wcsftime(str, count, format, time); // See above
std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
const std::tm*) = nullptr;
wcsftime = std::wcsftime;
return wcsftime(str, count, format, time);
}
FMT_END_DETAIL_NAMESPACE
template <typename Char, typename Duration>
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() {
this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
} }
} // namespace detail
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext> template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
if (it != ctx.end() && *it == ':') ++it; if (it != ctx.end() && *it == ':') ++it;
auto end = it; auto end = it;
while (end != ctx.end() && *end != '}') ++end; while (end != ctx.end() && *end != '}') ++end;
tm_format.reserve(detail::to_unsigned(end - it + 1)); if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
tm_format.append(it, end);
tm_format.push_back('\0');
return end; return end;
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { auto format(std::chrono::time_point<std::chrono::system_clock> val,
FormatContext& ctx) -> decltype(ctx.out()) {
std::tm time = localtime(val);
return formatter<std::tm, Char>::format(time, ctx);
}
static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
'%', 'd', ' ', '%', 'H', ':',
'%', 'M', ':', '%', 'S'};
};
template <typename Char, typename Duration>
constexpr Char
formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
Char>::default_specs[];
template <typename Char> struct formatter<std::tm, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it == ':') ++it;
auto end = it;
while (end != ctx.end() && *end != '}') ++end;
specs = {it, detail::to_unsigned(end - it)};
return end;
}
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) {
basic_memory_buffer<Char> tm_format;
tm_format.append(specs.begin(), specs.end());
// By appending an extra space we can distinguish an empty result that
// indicates insufficient buffer size from a guaranteed non-empty result
// https://github.com/fmtlib/fmt/issues/2238
tm_format.push_back(' ');
tm_format.push_back('\0');
basic_memory_buffer<Char> buf; basic_memory_buffer<Char> buf;
size_t start = buf.size(); size_t start = buf.size();
for (;;) { for (;;) {
@ -423,49 +538,40 @@ template <typename Char> struct formatter<std::tm, Char> {
buf.resize(start + count); buf.resize(start + count);
break; break;
} }
if (size >= tm_format.size() * 256) {
// If the buffer is 256 times larger than the format string, assume
// that `strftime` gives an empty result. There doesn't seem to be a
// better way to distinguish the two cases:
// https://github.com/fmtlib/fmt/issues/367
break;
}
const size_t MIN_GROWTH = 10; const size_t MIN_GROWTH = 10;
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
} }
return std::copy(buf.begin(), buf.end(), ctx.out()); // Remove the extra space.
return std::copy(buf.begin(), buf.end() - 1, ctx.out());
} }
basic_memory_buffer<Char> tm_format; basic_string_view<Char> specs;
}; };
namespace detail { FMT_BEGIN_DETAIL_NAMESPACE
template <typename Period> FMT_CONSTEXPR const char* get_units() {
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
if (std::is_same<Period, std::atto>::value) return "as";
if (std::is_same<Period, std::femto>::value) return "fs";
if (std::is_same<Period, std::pico>::value) return "ps";
if (std::is_same<Period, std::nano>::value) return "ns";
if (std::is_same<Period, std::micro>::value) return "µs";
if (std::is_same<Period, std::milli>::value) return "ms";
if (std::is_same<Period, std::centi>::value) return "cs";
if (std::is_same<Period, std::deci>::value) return "ds";
if (std::is_same<Period, std::ratio<1>>::value) return "s";
if (std::is_same<Period, std::deca>::value) return "das";
if (std::is_same<Period, std::hecto>::value) return "hs";
if (std::is_same<Period, std::kilo>::value) return "ks";
if (std::is_same<Period, std::mega>::value) return "Ms";
if (std::is_same<Period, std::giga>::value) return "Gs";
if (std::is_same<Period, std::tera>::value) return "Ts";
if (std::is_same<Period, std::peta>::value) return "Ps";
if (std::is_same<Period, std::exa>::value) return "Es";
if (std::is_same<Period, std::ratio<60>>::value) return "m";
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
return nullptr; return nullptr;
} }
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
return "m";
}
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
return "h";
}
enum class numeric_system { enum class numeric_system {
standard, standard,
@ -631,33 +737,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
return ptr; return ptr;
} }
struct chrono_format_checker { template <typename Derived> struct null_chrono_spec_handler {
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } FMT_CONSTEXPR void unsupported() {
static_cast<Derived*>(this)->unsupported();
}
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
FMT_CONSTEXPR void on_full_month() { unsupported(); }
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_us_date() { unsupported(); }
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
};
template <typename Char> void on_text(const Char*, const Char*) {} struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
FMT_NORETURN void on_abbr_weekday() { report_no_date(); } FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
FMT_NORETURN void on_full_weekday() { report_no_date(); }
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } template <typename Char>
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
FMT_NORETURN void on_abbr_month() { report_no_date(); } FMT_CONSTEXPR void on_24_hour(numeric_system) {}
FMT_NORETURN void on_full_month() { report_no_date(); } FMT_CONSTEXPR void on_12_hour(numeric_system) {}
void on_24_hour(numeric_system) {} FMT_CONSTEXPR void on_minute(numeric_system) {}
void on_12_hour(numeric_system) {} FMT_CONSTEXPR void on_second(numeric_system) {}
void on_minute(numeric_system) {} FMT_CONSTEXPR void on_12_hour_time() {}
void on_second(numeric_system) {} FMT_CONSTEXPR void on_24_hour_time() {}
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } FMT_CONSTEXPR void on_iso_time() {}
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } FMT_CONSTEXPR void on_am_pm() {}
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } FMT_CONSTEXPR void on_duration_value() {}
FMT_NORETURN void on_us_date() { report_no_date(); } FMT_CONSTEXPR void on_duration_unit() {}
FMT_NORETURN void on_iso_date() { report_no_date(); }
void on_12_hour_time() {}
void on_24_hour_time() {}
void on_iso_time() {}
void on_am_pm() {}
void on_duration_value() {}
void on_duration_unit() {}
FMT_NORETURN void on_utc_offset() { report_no_date(); }
FMT_NORETURN void on_tz_name() { report_no_date(); }
}; };
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
@ -681,7 +804,8 @@ inline bool isfinite(T value) {
// Converts value to int and checks that it's in the range [0, upper). // Converts value to int and checks that it's in the range [0, upper).
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline int to_nonnegative_int(T value, int upper) { inline int to_nonnegative_int(T value, int upper) {
FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
"invalid value");
(void)upper; (void)upper;
return static_cast<int>(value); return static_cast<int>(value);
} }
@ -759,15 +883,21 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
} }
template <typename Char, typename Rep, typename OutputIt> template <typename Char, typename Rep, typename OutputIt,
OutputIt format_duration_value(OutputIt out, Rep val, int precision) { FMT_ENABLE_IF(std::is_integral<Rep>::value)>
const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0}; OutputIt format_duration_value(OutputIt out, Rep val, int) {
if (precision >= 0) return format_to(out, pr_f, val, precision); return write<Char>(out, val);
const Char fp_f[] = {'{', ':', 'g', '}', 0};
const Char format[] = {'{', '}', 0};
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
val);
} }
template <typename Char, typename Rep, typename OutputIt,
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
auto specs = basic_format_specs<Char>();
specs.precision = precision;
specs.type = precision > 0 ? 'f' : 'g';
return write<Char>(out, val, specs);
}
template <typename Char, typename OutputIt> template <typename Char, typename OutputIt>
OutputIt copy_unit(string_view unit, OutputIt out, Char) { OutputIt copy_unit(string_view unit, OutputIt out, Char) {
return std::copy(unit.begin(), unit.end(), out); return std::copy(unit.begin(), unit.end(), out);
@ -785,10 +915,15 @@ template <typename Char, typename Period, typename OutputIt>
OutputIt format_duration_unit(OutputIt out) { OutputIt format_duration_unit(OutputIt out) {
if (const char* unit = get_units<Period>()) if (const char* unit = get_units<Period>())
return copy_unit(string_view(unit), out, Char()); return copy_unit(string_view(unit), out, Char());
const Char num_f[] = {'[', '{', '}', ']', 's', 0}; *out++ = '[';
if (const_check(Period::den == 1)) return format_to(out, num_f, Period::num); out = write<Char>(out, Period::num);
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; if (const_check(Period::den != 1)) {
return format_to(out, num_def_f, Period::num, Period::den); *out++ = '/';
out = write<Char>(out, Period::den);
}
*out++ = ']';
*out++ = 's';
return out;
} }
template <typename FormatContext, typename OutputIt, typename Rep, template <typename FormatContext, typename OutputIt, typename Rep,
@ -797,6 +932,7 @@ struct chrono_formatter {
FormatContext& context; FormatContext& context;
OutputIt out; OutputIt out;
int precision; int precision;
bool localized = false;
// rep is unsigned to avoid overflow. // rep is unsigned to avoid overflow.
using rep = using rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
@ -891,13 +1027,9 @@ struct chrono_formatter {
void format_localized(const tm& time, char format, char modifier = 0) { void format_localized(const tm& time, char format, char modifier = 0) {
if (isnan(val)) return write_nan(); if (isnan(val)) return write_nan();
auto locale = context.locale().template get<std::locale>(); const auto& loc = localized ? context.locale().template get<std::locale>()
auto& facet = std::use_facet<std::time_put<char_type>>(locale); : std::locale::classic();
std::basic_ostringstream<char_type> os; out = detail::write(out, time, loc, format, modifier);
os.imbue(locale);
facet.put(os, os, ' ', &time, format, modifier);
auto str = os.str();
std::copy(str.begin(), str.end(), out);
} }
void on_text(const char_type* begin, const char_type* end) { void on_text(const char_type* begin, const char_type* end) {
@ -1010,17 +1142,59 @@ struct chrono_formatter {
out = format_duration_unit<char_type, Period>(out); out = format_duration_unit<char_type, Period>(out);
} }
}; };
} // namespace detail
FMT_END_DETAIL_NAMESPACE
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
using weekday = std::chrono::weekday;
#else
// A fallback version of weekday.
class weekday {
private:
unsigned char value;
public:
weekday() = default;
explicit constexpr weekday(unsigned wd) noexcept
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
constexpr unsigned c_encoding() const noexcept { return value; }
};
#endif
// A rudimentary weekday formatter.
template <> struct formatter<weekday> {
private:
bool localized = false;
public:
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
auto begin = ctx.begin(), end = ctx.end();
if (begin != end && *begin == 'L') {
++begin;
localized = true;
}
return begin;
}
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding());
const auto& loc = localized ? ctx.locale().template get<std::locale>()
: std::locale::classic();
return detail::write(ctx.out(), time, loc, 'a');
}
};
template <typename Rep, typename Period, typename Char> template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> { struct formatter<std::chrono::duration<Rep, Period>, Char> {
private: private:
basic_format_specs<Char> specs; basic_format_specs<Char> specs;
int precision; int precision = -1;
using arg_ref_type = detail::arg_ref<Char>; using arg_ref_type = detail::arg_ref<Char>;
arg_ref_type width_ref; arg_ref_type width_ref;
arg_ref_type precision_ref; arg_ref_type precision_ref;
mutable basic_string_view<Char> format_str; bool localized = false;
basic_string_view<Char> format_str;
using duration = std::chrono::duration<Rep, Period>; using duration = std::chrono::duration<Rep, Period>;
struct spec_handler { struct spec_handler {
@ -1043,17 +1217,21 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
} }
void on_error(const char* msg) { FMT_THROW(format_error(msg)); } void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; } FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
void on_align(align_t align) { f.specs.align = align; } f.specs.fill = fill;
void on_width(int width) { f.specs.width = width; } }
void on_precision(int _precision) { f.precision = _precision; } FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
void end_precision() {} FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
FMT_CONSTEXPR void on_precision(int _precision) {
f.precision = _precision;
}
FMT_CONSTEXPR void end_precision() {}
template <typename Id> void on_dynamic_width(Id arg_id) { template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
f.width_ref = make_arg_ref(arg_id); f.width_ref = make_arg_ref(arg_id);
} }
template <typename Id> void on_dynamic_precision(Id arg_id) { template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
f.precision_ref = make_arg_ref(arg_id); f.precision_ref = make_arg_ref(arg_id);
} }
}; };
@ -1078,13 +1256,15 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
else else
handler.on_error("precision not allowed for this argument type"); handler.on_error("precision not allowed for this argument type");
} }
if (begin != end && *begin == 'L') {
++begin;
localized = true;
}
end = parse_chrono_format(begin, end, detail::chrono_format_checker()); end = parse_chrono_format(begin, end, detail::chrono_format_checker());
return {begin, end}; return {begin, end};
} }
public: public:
formatter() : precision(-1) {}
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) { -> decltype(ctx.begin()) {
auto range = do_parse(ctx); auto range = do_parse(ctx);
@ -1094,30 +1274,35 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const duration& d, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs_copy = specs;
auto precision_copy = precision;
auto begin = format_str.begin(), end = format_str.end(); auto begin = format_str.begin(), end = format_str.end();
// As a possible future optimization, we could avoid extra copying if width // As a possible future optimization, we could avoid extra copying if width
// is not specified. // is not specified.
basic_memory_buffer<Char> buf; basic_memory_buffer<Char> buf;
auto out = std::back_inserter(buf); auto out = std::back_inserter(buf);
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref, detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
ctx); width_ref, ctx);
detail::handle_dynamic_spec<detail::precision_checker>(precision, detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
precision_ref, ctx); precision_ref, ctx);
if (begin == end || *begin == '}') { if (begin == end || *begin == '}') {
out = detail::format_duration_value<Char>(out, d.count(), precision); out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
detail::format_duration_unit<Char, Period>(out); detail::format_duration_unit<Char, Period>(out);
} else { } else {
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
ctx, out, d); ctx, out, d);
f.precision = precision; f.precision = precision_copy;
parse_chrono_format(begin, end, f); f.localized = localized;
detail::parse_chrono_format(begin, end, f);
} }
return detail::write( return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs); ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
} }
}; };
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_CHRONO_H_ #endif // FMT_CHRONO_H_

View File

@ -10,7 +10,15 @@
#include "format.h" #include "format.h"
// __declspec(deprecated) is broken in some MSVC versions.
#if FMT_MSC_VER
# define FMT_DEPRECATED_NONMSVC
#else
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
enum class color : uint32_t { enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255) alice_blue = 0xF0F8FF, // rgb(240,248,255)
@ -198,7 +206,7 @@ struct rgb {
uint8_t b; uint8_t b;
}; };
namespace detail { FMT_BEGIN_DETAIL_NAMESPACE
// color is a struct of either a rgb color or a terminal color. // color is a struct of either a rgb color or a terminal color.
struct color_type { struct color_type {
@ -221,9 +229,10 @@ struct color_type {
uint32_t rgb_color; uint32_t rgb_color;
} value; } value;
}; };
} // namespace detail
// Experimental text formatting support. FMT_END_DETAIL_NAMESPACE
/** A text style consisting of foreground and background colors and emphasis. */
class text_style { class text_style {
public: public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
@ -260,33 +269,14 @@ class text_style {
return lhs |= rhs; return lhs |= rhs;
} }
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
const text_style& rhs) { const text_style& rhs) {
return lhs &= rhs; return and_assign(rhs);
}
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
operator&(text_style lhs, const text_style& rhs) {
return lhs.and_assign(rhs);
} }
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
@ -326,8 +316,34 @@ class text_style {
} }
} }
// DEPRECATED!
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
FMT_NOEXCEPT; FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
FMT_NOEXCEPT; FMT_NOEXCEPT;
@ -338,19 +354,22 @@ class text_style {
emphasis ems; emphasis ems;
}; };
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT { /** Creates a text style from the foreground (text) color. */
return text_style(/*is_foreground=*/true, foreground); FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
return text_style(true, foreground);
} }
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT { /** Creates a text style from the background color. */
return text_style(/*is_foreground=*/false, background); FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
return text_style(false, background);
} }
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs; return text_style(lhs) | rhs;
} }
namespace detail { FMT_BEGIN_DETAIL_NAMESPACE
template <typename Char> struct ansi_color_escape { template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
@ -358,7 +377,7 @@ template <typename Char> struct ansi_color_escape {
// If we have a terminal color, we need to output another escape code // If we have a terminal color, we need to output another escape code
// sequence. // sequence.
if (!text_color.is_rgb) { if (!text_color.is_rgb) {
bool is_background = esc == detail::data::background_color; bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color; uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with // Background ASCII codes are the same as the foreground ones but with
// 10 more. // 10 more.
@ -411,7 +430,7 @@ template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
return buffer + std::char_traits<Char>::length(buffer); return buffer + std::char_traits<Char>::length(buffer);
} }
@ -430,13 +449,13 @@ template <typename Char> struct ansi_color_escape {
template <typename Char> template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
detail::color_type foreground) FMT_NOEXCEPT { detail::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, detail::data::foreground_color); return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
} }
template <typename Char> template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
detail::color_type background) FMT_NOEXCEPT { detail::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, detail::data::background_color); return ansi_color_escape<Char>(background, "\x1b[48;2;");
} }
template <typename Char> template <typename Char>
@ -455,24 +474,23 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
} }
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT { template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::reset_color, stream); fputs("\x1b[0m", stream);
} }
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT { template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::wreset_color, stream); fputs(L"\x1b[0m", stream);
} }
template <typename Char> template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT { inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color; auto reset_color = string_view("\x1b[0m");
const char* end = begin + sizeof(data::reset_color) - 1; buffer.append(reset_color.begin(), reset_color.end());
buffer.append(begin, end);
} }
template <typename Char> template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts, void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str, basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
bool has_style = false; bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true; has_style = true;
@ -492,11 +510,12 @@ void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, format_str, args);
if (has_style) detail::reset_color<Char>(buf); if (has_style) detail::reset_color<Char>(buf);
} }
} // namespace detail
FMT_END_DETAIL_NAMESPACE
template <typename S, typename Char = char_t<S>> template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format, void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf; basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args); detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0)); buf.push_back(Char(0));
@ -504,28 +523,34 @@ void vprint(std::FILE* f, const text_style& ts, const S& format,
} }
/** /**
\rst
Formats a string and prints it to the specified file stream using ANSI Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting. escape sequences to specify text formatting.
Example:
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23); "Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/ */
template <typename S, typename... Args, template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)> FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str, void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) { const Args&... args) {
detail::check_format_string<Args...>(format_str); vprint(f, ts, format_str,
using context = buffer_context<char_t<S>>; fmt::make_args_checked<Args...>(format_str, args...));
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
} }
/** /**
\rst
Formats a string and prints it to stdout using ANSI escape sequences to Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting. specify text formatting.
Example:
**Example**::
fmt::print(fmt::emphasis::bold | fg(fmt::color::red), fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23); "Elapsed time: {0:.2f} seconds", 1.23);
\endrst
*/ */
template <typename S, typename... Args, template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)> FMT_ENABLE_IF(detail::is_string<S>::value)>
@ -557,10 +582,46 @@ inline std::basic_string<Char> vformat(
template <typename S, typename... Args, typename Char = char_t<S>> template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str, inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) { const Args&... args) {
return vformat(ts, to_string_view(format_str), return fmt::vformat(ts, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...)); fmt::make_args_checked<Args...>(format_str, args...));
} }
/**
Formats a string with the given text_style and writes the output to ``out``.
*/
template <typename OutputIt, typename Char,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
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);
}
/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
\endrst
*/
template <typename OutputIt, typename S, typename... Args,
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
detail::is_string<S>::value>
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
Args&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, ts, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COLOR_H_ #endif // FMT_COLOR_H_

View File

@ -8,13 +8,135 @@
#ifndef FMT_COMPILE_H_ #ifndef FMT_COMPILE_H_
#define FMT_COMPILE_H_ #define FMT_COMPILE_H_
#include <vector>
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// An output iterator that counts the number of objects written to it and
// discards them.
class counting_iterator {
private:
size_t count_;
public:
using iterator_category = std::output_iterator_tag;
using difference_type = std::ptrdiff_t;
using pointer = void;
using reference = void;
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
struct value_type {
template <typename T> void operator=(const T&) {}
};
counting_iterator() : count_(0) {}
size_t count() const { return count_; }
counting_iterator& operator++() {
++count_;
return *this;
}
counting_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
friend counting_iterator operator+(counting_iterator it, difference_type n) {
it.count_ += static_cast<size_t>(n);
return it;
}
value_type operator*() const { return {}; }
};
template <typename Char, typename InputIt>
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;
using _Unchecked_type =
truncating_iterator_base; // Mark iterator as checked.
OutputIt base() const { return out_; }
size_t count() const { return count_; }
};
// An output iterator that truncates the output and counts the number of objects
// written to it.
template <typename OutputIt,
typename Enable = typename std::is_void<
typename std::iterator_traits<OutputIt>::value_type>::type>
class truncating_iterator;
template <typename OutputIt>
class truncating_iterator<OutputIt, std::false_type>
: public truncating_iterator_base<OutputIt> {
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
public:
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
truncating_iterator& operator++() {
if (this->count_++ < this->limit_) ++this->out_;
return *this;
}
truncating_iterator operator++(int) {
auto it = *this;
++*this;
return it;
}
value_type& operator*() const {
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
}
};
template <typename OutputIt>
class truncating_iterator<OutputIt, std::true_type>
: public truncating_iterator_base<OutputIt> {
public:
truncating_iterator() = default;
truncating_iterator(OutputIt out, size_t limit)
: truncating_iterator_base<OutputIt>(out, limit) {}
template <typename T> truncating_iterator& operator=(T val) {
if (this->count_++ < this->limit_) *this->out_++ = val;
return *this;
}
truncating_iterator& operator++() { return *this; }
truncating_iterator& operator++(int) { return *this; }
truncating_iterator& operator*() { return *this; }
};
// A compile-time string which is compiled into fast formatting code. // A compile-time string which is compiled into fast formatting code.
class compiled_string {}; class compiled_string {};
@ -34,341 +156,36 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
std::string s = fmt::format(FMT_COMPILE("{}"), 42); std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst \endrst
*/ */
#define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::detail::compiled_string) #ifdef __cpp_if_constexpr
# define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail> template <typename T, typename... Tail>
const T& first(const T& value, const Tail&...) { const T& first(const T& value, const Tail&...) {
return value; return value;
} }
// Part of a compiled format string. It can be either literal text or a
// replacement field.
template <typename Char> struct format_part {
enum class kind { arg_index, arg_name, text, replacement };
struct replacement {
arg_ref<Char> arg_id;
dynamic_format_specs<Char> specs;
};
kind part_kind;
union value {
int arg_index;
basic_string_view<Char> str;
replacement repl;
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
FMT_CONSTEXPR value(replacement r) : repl(r) {}
} val;
// Position past the end of the argument id.
const Char* arg_id_end = nullptr;
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
: part_kind(k), val(v) {}
static FMT_CONSTEXPR format_part make_arg_index(int index) {
return format_part(kind::arg_index, index);
}
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
return format_part(kind::arg_name, name);
}
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
return format_part(kind::text, text);
}
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
return format_part(kind::replacement, repl);
}
};
template <typename Char> struct part_counter {
unsigned num_parts = 0;
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end) ++num_parts;
}
FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; }
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char>) {
return ++num_parts, 0;
}
FMT_CONSTEXPR void on_replacement_field(int, const Char*) {}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
// Find the matching brace.
unsigned brace_counter = 0;
for (; begin != end; ++begin) {
if (*begin == '{') {
++brace_counter;
} else if (*begin == '}') {
if (brace_counter == 0u) break;
--brace_counter;
}
}
return begin;
}
FMT_CONSTEXPR void on_error(const char*) {}
};
// Counts the number of parts in a format string.
template <typename Char>
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
part_counter<Char> counter;
parse_format_string<true>(format_str, counter);
return counter.num_parts;
}
template <typename Char, typename PartHandler>
class format_string_compiler : public error_handler {
private:
using part = format_part<Char>;
PartHandler handler_;
part part_;
basic_string_view<Char> format_str_;
basic_format_parse_context<Char> parse_context_;
public:
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
PartHandler handler)
: handler_(handler),
format_str_(format_str),
parse_context_(format_str) {}
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
if (begin != end)
handler_(part::make_text({begin, to_unsigned(end - begin)}));
}
FMT_CONSTEXPR int on_arg_id() {
part_ = part::make_arg_index(parse_context_.next_arg_id());
return 0;
}
FMT_CONSTEXPR int on_arg_id(int id) {
parse_context_.check_arg_id(id);
part_ = part::make_arg_index(id);
return 0;
}
FMT_CONSTEXPR int on_arg_id(basic_string_view<Char> id) {
part_ = part::make_arg_name(id);
return 0;
}
FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) {
part_.arg_id_end = ptr;
handler_(part_);
}
FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin,
const Char* end) {
auto repl = typename part::replacement();
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
repl.specs, parse_context_);
auto it = parse_format_specs(begin, end, handler);
if (*it != '}') on_error("missing '}' in format string");
repl.arg_id = part_.part_kind == part::kind::arg_index
? arg_ref<Char>(part_.val.arg_index)
: arg_ref<Char>(part_.val.str);
auto part = part::make_replacement(repl);
part.arg_id_end = begin;
handler_(part);
return it;
}
};
// Compiles a format string and invokes handler(part) for each parsed part.
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
PartHandler handler) {
parse_format_string<IS_CONSTEXPR>(
format_str,
format_string_compiler<Char, PartHandler>(format_str, handler));
}
template <typename OutputIt, typename Context, typename Id>
void format_arg(
basic_format_parse_context<typename Context::char_type>& parse_ctx,
Context& ctx, Id arg_id) {
ctx.advance_to(visit_format_arg(
arg_formatter<OutputIt, typename Context::char_type>(ctx, &parse_ctx),
ctx.arg(arg_id)));
}
// vformat_to is defined in a subnamespace to prevent ADL.
namespace cf {
template <typename Context, typename OutputIt, typename CompiledFormat>
auto vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) -> typename Context::iterator {
using char_type = typename Context::char_type;
basic_format_parse_context<char_type> parse_ctx(
to_string_view(cf.format_str_));
Context ctx(out, args);
const auto& parts = cf.parts();
for (auto part_it = std::begin(parts); part_it != std::end(parts);
++part_it) {
const auto& part = *part_it;
const auto& value = part.val;
using format_part_t = format_part<char_type>;
switch (part.part_kind) {
case format_part_t::kind::text: {
const auto text = value.str;
auto output = ctx.out();
auto&& it = reserve(output, text.size());
it = std::copy_n(text.begin(), text.size(), it);
ctx.advance_to(output);
break;
}
case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.arg_index);
break;
case format_part_t::kind::arg_name:
advance_to(parse_ctx, part.arg_id_end);
detail::format_arg<OutputIt>(parse_ctx, ctx, value.str);
break;
case format_part_t::kind::replacement: {
const auto& arg_id_value = value.repl.arg_id.val;
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
? ctx.arg(arg_id_value.index)
: ctx.arg(arg_id_value.name);
auto specs = value.repl.specs;
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
handle_dynamic_spec<precision_checker>(specs.precision,
specs.precision_ref, ctx);
error_handler h;
numeric_specs_checker<error_handler> checker(h, arg.type());
if (specs.align == align::numeric) checker.require_numeric_argument();
if (specs.sign != sign::none) checker.check_sign();
if (specs.alt) checker.require_numeric_argument();
if (specs.precision >= 0) checker.check_precision();
advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to(
visit_format_arg(arg_formatter<OutputIt, typename Context::char_type>(
ctx, nullptr, &specs),
arg));
break;
}
}
}
return ctx.out();
}
} // namespace cf
struct basic_compiled_format {};
template <typename S, typename = void>
struct compiled_format_base : basic_compiled_format {
using char_type = char_t<S>;
using parts_container = std::vector<detail::format_part<char_type>>;
parts_container compiled_parts;
explicit compiled_format_base(basic_string_view<char_type> format_str) {
compile_format_string<false>(format_str,
[this](const format_part<char_type>& part) {
compiled_parts.push_back(part);
});
}
const parts_container& parts() const { return compiled_parts; }
};
template <typename Char, unsigned N> struct format_part_array {
format_part<Char> data[N] = {};
FMT_CONSTEXPR format_part_array() = default;
};
template <typename Char, unsigned N>
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
basic_string_view<Char> format_str) {
format_part_array<Char, N> parts;
unsigned counter = 0;
// This is not a lambda for compatibility with older compilers.
struct {
format_part<Char>* parts;
unsigned* counter;
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
parts[(*counter)++] = part;
}
} collector{parts.data, &counter};
compile_format_string<true>(format_str, collector);
if (counter < N) {
parts.data[counter] =
format_part<Char>::make_text(basic_string_view<Char>());
}
return parts;
}
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename S>
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
: basic_compiled_format {
using char_type = char_t<S>;
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
// Workaround for old compilers. Format string compilation will not be
// performed there anyway.
#if FMT_USE_CONSTEXPR
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
constexpr_max(count_parts(to_string_view(S())), 1u);
#else
static const unsigned num_format_parts = 1;
#endif
using parts_container = format_part<char_type>[num_format_parts];
const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const auto compiled_parts =
compile_to_parts<char_type, num_format_parts>(
detail::to_string_view(S()));
return compiled_parts.data;
}
};
template <typename S, typename... Args>
class compiled_format : private compiled_format_base<S> {
public:
using typename compiled_format_base<S>::char_type;
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename OutputIt, typename CompiledFormat>
friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
public:
compiled_format() = delete;
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
: compiled_format_base<S>(format_str), format_str_(format_str) {}
};
#ifdef __cpp_if_constexpr #ifdef __cpp_if_constexpr
template <typename... Args> struct type_list {}; template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args> template <int N, typename T, typename... Args>
constexpr const auto& get(const T& first, const Args&... rest) { constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
@ -376,6 +193,12 @@ constexpr const auto& get(const T& first, const Args&... rest) {
return get<N - 1>(rest...); return get<N - 1>(rest...);
} }
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
return get_arg_index_by_name<Args...>(name);
}
template <int N, typename> struct get_type_impl; template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
@ -392,7 +215,7 @@ template <typename Char> struct text {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&...) const { constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, data); return write<Char>(out, data);
} }
}; };
@ -406,32 +229,87 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
return {{&s[pos], size}}; return {{&s[pos], size}};
} }
template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
return write<Char>(out, value);
}
};
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
return arg;
}
}
template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename T, int N> struct field {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`. return write<Char>(out, get_arg_checked<T, N>(args...));
const T& arg = get<N>(args...);
return write<Char>(out, arg);
} }
}; };
template <typename Char, typename T, int N> template <typename Char, typename T, int N>
struct is_compiled_format<field<Char, T, N>> : std::true_type {}; struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument with name.
template <typename Char> struct runtime_named_field {
using char_type = Char;
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
return true;
}
}
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
throw format_error("argument with specified name is not found");
}
return out;
}
};
template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename T, int N> struct spec_field {
using char_type = Char; using char_type = Char;
mutable formatter<T, Char> fmt; formatter<T, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const { constexpr FMT_INLINE OutputIt format(OutputIt out,
// This ensures that the argument type is convertile to `const T&`. const Args&... args) const {
const T& arg = get<N>(args...); const auto& vargs =
basic_format_context<OutputIt, Char> ctx(out, {}); fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
return fmt.format(arg, ctx); basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
} }
}; };
@ -444,7 +322,7 @@ template <typename L, typename R> struct concat {
using char_type = typename L::char_type; using char_type = typename L::char_type;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
out = lhs.format(out, args...); out = lhs.format(out, args...);
return rhs.format(out, args...); return rhs.format(out, args...);
} }
@ -489,16 +367,80 @@ constexpr auto parse_tail(T head, S format_str) {
template <typename T, typename Char> struct parse_specs_result { template <typename T, typename Char> struct parse_specs_result {
formatter<T, Char> fmt; formatter<T, Char> fmt;
size_t end; size_t end;
int next_arg_id;
}; };
constexpr int manual_indexing_id = -1;
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos) { size_t pos, int next_arg_id) {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = basic_format_parse_context<Char>(str); auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
auto f = formatter<T, Char>(); auto f = formatter<T, Char>();
auto end = f.parse(ctx); auto end = f.parse(ctx);
return {f, pos + (end - str.data()) + 1}; return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
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()() {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int operator()(int id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int operator()(basic_string_view<Char> id) {
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr void on_error(const char* message) { throw format_error(message); }
};
template <typename Char> struct parse_arg_id_result {
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> struct field_type {
using type = remove_cvref_t<T>;
};
template <typename T>
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
using type = remove_cvref_t<decltype(T::value)>;
};
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
typename S>
constexpr auto parse_replacement_field_then_tail(S format_str) {
using char_type = typename S::char_type;
constexpr auto str = basic_string_view<char_type>(format_str);
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
if constexpr (c == '}') {
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 == ':') {
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>(
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 // Compiles a non-empty format string and returns the compiled representation
@ -506,79 +448,90 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
template <typename Args, size_t POS, int ID, typename S> template <typename Args, size_t POS, int ID, typename S>
constexpr auto compile_format_string(S format_str) { constexpr auto compile_format_string(S format_str) {
using char_type = typename S::char_type; using char_type = typename S::char_type;
constexpr basic_string_view<char_type> str = format_str; constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') { if constexpr (str[POS] == '{') {
if (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '{' in format string"); throw format_error("unmatched '{' in format string");
if constexpr (str[POS + 1] == '{') { if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}') { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
using type = get_type<ID, Args>; static_assert(ID != manual_indexing_id,
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(), "cannot switch from manual to automatic argument indexing");
constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
POS + 1, ID, next_id>(
format_str); format_str);
} else if constexpr (str[POS + 1] == ':') {
using type = get_type<ID, Args>;
constexpr auto result = parse_specs<type>(str, POS + 2);
return parse_tail<Args, result.end, ID + 1>(
spec_field<char_type, type, ID>{result.fmt}, format_str);
} else { } else {
return unknown_format(); constexpr auto arg_id_result =
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
static_assert(
ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos,
arg_index, manual_indexing_id>(
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) {
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 == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
format_str);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}
}
}
} }
} else if constexpr (str[POS] == '}') { } else if constexpr (str[POS] == '}') {
if (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '}' in format string"); throw format_error("unmatched '}' in format string");
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else { } else {
constexpr auto end = parse_text(str, POS + 1); constexpr auto end = parse_text(str, POS + 1);
if constexpr (end - POS > 1) {
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
format_str); format_str);
} else {
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
format_str);
}
} }
} }
template <typename... Args, typename S, template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value || FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
detail::is_compiled_string<S>::value)>
constexpr auto compile(S format_str) { constexpr auto compile(S format_str) {
constexpr basic_string_view<typename S::char_type> str = format_str; constexpr auto str = basic_string_view<typename S::char_type>(format_str);
if constexpr (str.size() == 0) { if constexpr (str.size() == 0) {
return detail::make_text(str, 0, 0); return detail::make_text(str, 0, 0);
} else { } else {
constexpr auto result = constexpr auto result =
detail::compile_format_string<detail::type_list<Args...>, 0, 0>( detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
format_str); format_str);
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
detail::unknown_format>()) {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
} else {
return result; return result;
} }
} }
}
#else
template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)>
constexpr auto compile(S format_str) -> detail::compiled_format<S, Args...> {
return detail::compiled_format<S, Args...>(to_string_view(format_str));
}
#endif // __cpp_if_constexpr #endif // __cpp_if_constexpr
// Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N])
-> detail::compiled_format<const Char*, Args...> {
return detail::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
}
} // namespace detail } // namespace detail
// DEPRECATED! use FMT_COMPILE instead. FMT_MODULE_EXPORT_BEGIN
template <typename... Args>
FMT_DEPRECATED auto compile(const Args&... args)
-> decltype(detail::compile(args...)) {
return detail::compile(args...);
}
#if FMT_USE_CONSTEXPR
#ifdef __cpp_if_constexpr #ifdef __cpp_if_constexpr
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... Args,
@ -586,80 +539,101 @@ template <typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) { const Args&... args) {
basic_memory_buffer<Char> buffer; auto s = std::basic_string<Char>();
detail::buffer<Char>& base = buffer; cf.format(std::back_inserter(s), args...);
cf.format(std::back_inserter(base), args...); return s;
return to_string(buffer);
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf, constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const Args&... args) {
return cf.format(out, args...); return cf.format(out, args...);
} }
# endif // __cpp_if_constexpr
#endif // FMT_USE_CONSTEXPR
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
basic_memory_buffer<Char> buffer;
using context = buffer_context<Char>;
detail::buffer<Char>& base = buffer;
detail::cf::vformat_to<context>(std::back_inserter(base), cf,
make_format_args<context>(args...));
return to_string(buffer);
}
template <typename S, typename... Args, template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&, FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) { Args&&... args) {
constexpr basic_string_view<typename S::char_type> str = S(); if constexpr (std::is_same<typename S::char_type, char>::value) {
if (str.size() == 2 && str[0] == '{' && str[1] == '}') constexpr auto str = basic_string_view<typename S::char_type>(S());
return fmt::to_string(detail::first(args...)); if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
const auto& first = detail::first(args...);
if constexpr (detail::is_named_arg<
remove_cvref_t<decltype(first)>>::value) {
return fmt::to_string(first.value);
} else {
return fmt::to_string(first);
}
}
}
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return format(compiled, std::forward<Args>(args)...); return format(compiled, std::forward<Args>(args)...);
} }
template <typename OutputIt, typename CompiledFormat, typename... Args,
FMT_ENABLE_IF(std::is_base_of<detail::basic_compiled_format,
CompiledFormat>::value)>
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
using char_type = typename CompiledFormat::char_type;
using context = format_context_t<OutputIt, char_type>;
return detail::cf::vformat_to<context>(out, cf,
make_format_args<context>(args...));
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
OutputIt format_to(OutputIt out, const S&, const Args&... args) { FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<Args...>(S());
return format_to(out, compiled, args...); if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return format_to(out,
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
} else {
return format_to(out, compiled, std::forward<Args>(args)...);
} }
}
#endif
template < template <typename OutputIt, typename S, typename... Args,
typename OutputIt, typename CompiledFormat, typename... Args, FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&& std::is_base_of<
detail::basic_compiled_format, CompiledFormat>::value)>
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
const CompiledFormat& cf, const S& format_str, Args&&... args) {
const Args&... args) { auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
auto it = std::forward<Args>(args)...);
format_to(detail::truncating_iterator<OutputIt>(out, n), cf, args...);
return {it.base(), it.count()}; return {it.base(), it.count()};
} }
template <typename CompiledFormat, typename... Args> template <typename S, typename... Args,
size_t formatted_size(const CompiledFormat& cf, const Args&... args) { FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
return format_to(detail::counting_iterator(), cf, args...).count(); size_t formatted_size(const S& format_str, const Args&... args) {
return format_to(detail::counting_iterator(), format_str, args...).count();
} }
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& format_str, const Args&... args) {
memory_buffer buffer;
format_to(std::back_inserter(buffer), format_str, args...);
detail::print(f, {buffer.data(), buffer.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& format_str, const Args&... args) {
print(stdout, format_str, args...);
}
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
inline namespace literals {
template <detail_exported::fixed_string Str>
constexpr detail::udl_compiled_string<
remove_cvref_t<decltype(Str.data[0])>,
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
operator""_cf() {
return {};
}
} // namespace literals
#endif
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_COMPILE_H_ #endif // FMT_COMPILE_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +1,2 @@
// Formatting library for C++ - std::locale support #include "xchar.h"
// #warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_LOCALE_H_
#define FMT_LOCALE_H_
#include <locale>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char>
typename buffer_context<Char>::iterator vformat_to(
const std::locale& loc, buffer<Char>& buf,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
using af = arg_formatter<typename buffer_context<Char>::iterator, Char>;
return vformat_to<af>(std::back_inserter(buf), to_string_view(format_str),
args, detail::locale_ref(loc));
}
template <typename Char>
std::basic_string<Char> vformat(
const std::locale& loc, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer;
detail::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
}
} // namespace detail
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const std::locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
return detail::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const std::locale& loc,
const S& format_str, Args&&... args) {
return detail::vformat(
loc, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
}
template <typename S, typename OutputIt, typename... Args,
typename Char = enable_if_t<
detail::is_output_iterator<OutputIt>::value, char_t<S>>>
inline OutputIt vformat_to(
OutputIt out, const std::locale& loc, const S& format_str,
format_args_t<type_identity_t<OutputIt>, Char> args) {
using af = detail::arg_formatter<OutputIt, Char>;
return vformat_to<af>(out, to_string_view(format_str), args,
detail::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const std::locale& loc,
const S& format_str, Args&&... args) {
detail::check_format_string<Args...>(format_str);
using context = format_context_t<OutputIt, char_t<S>>;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE
#endif // FMT_LOCALE_H_

View File

@ -8,16 +8,12 @@
#ifndef FMT_OS_H_ #ifndef FMT_OS_H_
#define FMT_OS_H_ #define FMT_OS_H_
#if defined(__MINGW32__) || defined(__CYGWIN__)
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
# undef __STRICT_ANSI__
#endif
#include <cerrno> #include <cerrno>
#include <clocale> // for locale_t #include <clocale> // locale_t
#include <cstddef> #include <cstddef>
#include <cstdio> #include <cstdio>
#include <cstdlib> // for strtod_l #include <cstdlib> // strtod_l
#include <system_error> // std::system_error
#if defined __APPLE__ || defined(__FreeBSD__) #if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X # include <xlocale.h> // for LC_NUMERIC_MASK on OS X
@ -29,7 +25,8 @@
#if FMT_HAS_INCLUDE("winapifamily.h") #if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h> # include <winapifamily.h>
#endif #endif
#if FMT_HAS_INCLUDE("fcntl.h") && \ #if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
@ -73,6 +70,7 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_MODULE_EXPORT_BEGIN
/** /**
\rst \rst
@ -121,19 +119,28 @@ template <typename Char> class basic_cstring_view {
using cstring_view = basic_cstring_view<char>; using cstring_view = basic_cstring_view<char>;
using wcstring_view = basic_cstring_view<wchar_t>; using wcstring_view = basic_cstring_view<wchar_t>;
// An error code. template <typename Char> struct formatter<std::error_code, Char> {
class error_code { template <typename ParseContext>
private: FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
int value_; return ctx.begin();
}
public: template <typename FormatContext>
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
-> decltype(ctx.out()) {
int get() const FMT_NOEXCEPT { return value_; } auto out = ctx.out();
out = detail::write_bytes(out, ec.category().name(),
basic_format_specs<Char>());
out = detail::write<Char>(out, Char(':'));
out = detail::write<Char>(out, ec.value());
return out;
}
}; };
#ifdef _WIN32 #ifdef _WIN32
namespace detail { FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
FMT_BEGIN_DETAIL_NAMESPACE
// A converter from UTF-16 to UTF-8. // A converter from UTF-16 to UTF-8.
// It is only provided for Windows since other systems support UTF-8 natively. // It is only provided for Windows since other systems support UTF-8 natively.
class utf16_to_utf8 { class utf16_to_utf8 {
@ -142,7 +149,7 @@ class utf16_to_utf8 {
public: public:
utf16_to_utf8() {} utf16_to_utf8() {}
FMT_API explicit utf16_to_utf8(wstring_view s); FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
operator string_view() const { return string_view(&buffer_[0], size()); } operator string_view() const { return string_view(&buffer_[0], size()); }
size_t size() const { return buffer_.size() - 1; } size_t size() const { return buffer_.size() - 1; }
const char* c_str() const { return &buffer_[0]; } const char* c_str() const { return &buffer_[0]; }
@ -151,22 +158,19 @@ class utf16_to_utf8 {
// Performs conversion returning a system error code instead of // Performs conversion returning a system error code instead of
// throwing exception on conversion error. This method may still throw // throwing exception on conversion error. This method may still throw
// in case of memory allocation error. // in case of memory allocation error.
FMT_API int convert(wstring_view s); FMT_API int convert(basic_string_view<wchar_t> s);
}; };
FMT_API void format_windows_error(buffer<char>& out, int error_code, FMT_API void format_windows_error(buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT; const char* message) FMT_NOEXCEPT;
} // namespace detail FMT_END_DETAIL_NAMESPACE
/** A Windows error. */ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
class windows_error : public system_error { format_args args);
private:
FMT_API void init(int error_code, string_view format_str, format_args args);
public:
/** /**
\rst \rst
Constructs a :class:`fmt::windows_error` object with the description Constructs a :class:`std::system_error` object with the description
of the form of the form
.. parsed-literal:: .. parsed-literal::
@ -180,7 +184,7 @@ class windows_error : public system_error {
**Example**:: **Example**::
// This throws a windows_error with the description // This throws a system_error with the description
// cannot open file 'madeup': The system cannot find the file specified. // cannot open file 'madeup': The system cannot find the file specified.
// or similar (system message may vary). // or similar (system message may vary).
const char *filename = "madeup"; const char *filename = "madeup";
@ -193,17 +197,29 @@ class windows_error : public system_error {
\endrst \endrst
*/ */
template <typename... Args> template <typename... Args>
windows_error(int error_code, string_view message, const Args&... args) { std::system_error windows_error(int error_code, string_view message,
init(error_code, message, make_format_args(args...)); const Args&... args) {
return vwindows_error(error_code, message, fmt::make_format_args(args...));
} }
};
// Reports a Windows error without throwing an exception. // Reports a Windows error without throwing an exception.
// Can be used to report errors from destructors. // Can be used to report errors from destructors.
FMT_API void report_windows_error(int error_code, FMT_API void report_windows_error(int error_code,
string_view message) FMT_NOEXCEPT; const char* message) FMT_NOEXCEPT;
#else
inline const std::error_category& system_category() FMT_NOEXCEPT {
return std::system_category();
}
#endif // _WIN32 #endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
}
#endif
// A buffered file. // A buffered file.
class buffered_file { class buffered_file {
private: private:
@ -254,7 +270,7 @@ class buffered_file {
template <typename... Args> template <typename... Args>
inline void print(string_view format_str, const Args&... args) { inline void print(string_view format_str, const Args&... args) {
vprint(format_str, make_format_args(args...)); vprint(format_str, fmt::make_format_args(args...));
} }
}; };
@ -278,7 +294,9 @@ class file {
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing. RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
CREATE = FMT_POSIX(O_CREAT) // Create if the file doesn't exist. CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
}; };
// Constructs a file object which doesn't represent any file. // Constructs a file object which doesn't represent any file.
@ -293,7 +311,8 @@ class file {
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
file& operator=(file&& other) FMT_NOEXCEPT { // Move assignment is not noexcept because close may throw.
file& operator=(file&& other) {
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
@ -329,7 +348,7 @@ class file {
// Makes fd be the copy of this file descriptor, closing fd first if // Makes fd be the copy of this file descriptor, closing fd first if
// necessary. // necessary.
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT; FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
// Creates a pipe setting up read_end and write_end file objects for reading // Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively. // and writing respectively.
@ -343,62 +362,107 @@ class file {
// Returns the memory page size. // Returns the memory page size.
long getpagesize(); long getpagesize();
class direct_buffered_file; FMT_BEGIN_DETAIL_NAMESPACE
template <typename S, typename... Args> struct buffer_size {
void print(direct_buffered_file& f, const S& format_str, buffer_size() = default;
const Args&... args); size_t value = 0;
buffer_size operator=(size_t val) const {
auto bs = buffer_size();
bs.value = val;
return bs;
}
};
// A buffered file with a direct buffer access and no synchronization. struct ostream_params {
class direct_buffered_file { int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {}
template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
oflag = new_oflag;
}
template <typename... T>
ostream_params(T... params, detail::buffer_size bs)
: ostream_params(params...) {
this->buffer_size = bs.value;
}
};
FMT_END_DETAIL_NAMESPACE
static constexpr detail::buffer_size buffer_size;
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
private: private:
file file_; file file_;
enum { buffer_size = 4096 };
char buffer_[buffer_size];
int pos_;
void flush() { void flush() {
if (pos_ == 0) return; if (size() == 0) return;
file_.write(buffer_, pos_); file_.write(data(), size());
pos_ = 0; clear();
} }
int free_capacity() const { return buffer_size - pos_; } void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params)
: file_(path, params.oflag) {
set(new char[params.buffer_size], params.buffer_size);
}
public: public:
direct_buffered_file(cstring_view path, int oflag) ostream(ostream&& other)
: file_(path, oflag), pos_(0) {} : detail::buffer<char>(other.data(), other.size(), other.capacity()),
file_(std::move(other.file_)) {
~direct_buffered_file() { other.clear();
flush(); other.set(nullptr, 0);
} }
~ostream() {
flush();
delete[] data();
}
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
void close() { void close() {
flush(); flush();
file_.close(); file_.close();
} }
template <typename S, typename... Args> /**
friend void print(direct_buffered_file& f, const S& format_str, Formats ``args`` according to specifications in ``fmt`` and writes the
const Args&... args) { output to the file.
// We could avoid double buffering. */
auto buf = fmt::memory_buffer(); template <typename... T> void print(format_string<T...> fmt, T&&... args) {
fmt::format_to(std::back_inserter(buf), format_str, args...); vformat_to(detail::buffer_appender<char>(*this), fmt,
auto remaining_pos = 0; fmt::make_format_args(args...));
auto remaining_size = buf.size();
while (remaining_size > detail::to_unsigned(f.free_capacity())) {
auto size = f.free_capacity();
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, size);
f.pos_ += size;
f.flush();
remaining_pos += size;
remaining_size -= size;
}
memcpy(f.buffer_ + f.pos_, buf.data() + remaining_pos, remaining_size);
f.pos_ += static_cast<int>(remaining_size);
} }
}; };
/**
\rst
Opens a file for writing. Supported parameters passed in *params*:
* ``<integer>``: Flags passed to `open
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
(``file::WRONLY | file::CREATE`` by default)
* ``buffer_size=<integer>``: Output buffer size
**Example**::
auto out = fmt::output_file("guide.txt");
out.print("Don't {}", "Panic");
\endrst
*/
template <typename... T>
inline ostream output_file(cstring_view path, T... params) {
return {path, detail::ostream_params(params...)};
}
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
@ -445,6 +509,7 @@ class locale {
}; };
using Locale FMT_DEPRECATED_ALIAS = locale; using Locale FMT_DEPRECATED_ALIAS = locale;
#endif // FMT_LOCALE #endif // FMT_LOCALE
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_OS_H_ #endif // FMT_OS_H_

View File

@ -49,17 +49,27 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
} }
}; };
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> { template <typename Char> struct test_stream : std::basic_ostream<Char> {
private: private:
// Hide all operator<< from std::basic_ostream<Char>. void_t<> operator<<(converter);
void_t<> operator<<(null<>);
void_t<> operator<<(const Char*);
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>
void_t<> operator<<(T);
}; };
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of // Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream). // std::ostream).
template <typename T, typename Char> class is_streamable { template <typename T, typename Char> class is_streamable {
@ -75,6 +85,8 @@ template <typename T, typename Char> class is_streamable {
using result = decltype(test<T>(0)); using result = decltype(test<T>(0));
public: public:
is_streamable() = default;
static const bool value = result::value; static const bool value = result::value;
}; };
@ -103,7 +115,7 @@ void format_value(buffer<Char>& buf, const T& value,
#endif #endif
output << value; output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
buf.resize(buf.size()); buf.try_resize(buf.size());
} }
// Formats an object of type T that has an overloaded ostream operator<<. // Formats an object of type T that has an overloaded ostream operator<<.
@ -139,6 +151,7 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
}; };
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT
template <typename Char> template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
@ -156,11 +169,12 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
fmt::print(cerr, "Don't {}!", "panic"); fmt::print(cerr, "Don't {}!", "panic");
\endrst \endrst
*/ */
FMT_MODULE_EXPORT
template <typename S, typename... Args, template <typename S, typename... Args,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
vprint(os, to_string_view(format_str), vprint(os, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...)); fmt::make_args_checked<Args...>(format_str, args...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -10,11 +10,54 @@
#include <algorithm> // std::max #include <algorithm> // std::max
#include <limits> // std::numeric_limits #include <limits> // std::numeric_limits
#include <ostream>
#include "ostream.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { FMT_MODULE_EXPORT_BEGIN
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 {
private:
OutputIt 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>;
template <typename T> using formatter_type = printf_formatter<T>;
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
FMT_CONSTEXPR void on_error(const char* message) {
detail::error_handler().on_error(message);
}
};
FMT_BEGIN_DETAIL_NAMESPACE
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
@ -178,79 +221,34 @@ template <typename Char> class printf_width_handler {
} }
}; };
template <typename Char, typename Context> // The ``printf`` argument formatter.
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
basic_format_args<Context> args) {
Context(std::back_inserter(buf), format, args).format();
}
} // namespace detail
// For printing into memory_buffer.
template <typename Char, typename Context>
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
basic_string_view<Char> format,
basic_format_args<Context> args) {
return detail::vprintf(buf, format, args);
}
using detail::vprintf;
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;
/**
\rst
The ``printf`` argument formatter.
\endrst
*/
template <typename OutputIt, typename Char> template <typename OutputIt, typename Char>
class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> { class printf_arg_formatter : public arg_formatter<Char> {
public:
using iterator = OutputIt;
private: private:
using char_type = Char; using base = arg_formatter<Char>;
using base = detail::arg_formatter_base<OutputIt, Char>;
using context_type = basic_printf_context<OutputIt, Char>; using context_type = basic_printf_context<OutputIt, Char>;
using format_specs = basic_format_specs<Char>;
context_type& context_; context_type& context_;
void write_null_pointer(char) { OutputIt write_null_pointer(bool is_string = false) {
this->specs()->type = 0; auto s = this->specs;
this->write("(nil)"); s.type = 0;
} return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
void write_null_pointer(wchar_t) {
this->specs()->type = 0;
this->write(L"(nil)");
} }
public: public:
using format_specs = typename base::format_specs; printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
: base{iter, s, locale_ref()}, context_(ctx) {}
/** OutputIt operator()(monostate value) { return base::operator()(value); }
\rst
Constructs an argument formatter object.
*buffer* is a reference to the output buffer and *specs* contains format
specifier information for standard argument types.
\endrst
*/
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
: base(iter, &specs, detail::locale_ref()), context_(ctx) {}
template <typename T, FMT_ENABLE_IF(fmt::detail::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
iterator operator()(T value) { OutputIt operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and char_type so // MSVC2013 fails to compile separate overloads for bool and Char so use
// use std::is_same instead. // std::is_same instead.
if (std::is_same<T, bool>::value) { if (std::is_same<T, Char>::value) {
format_specs& fmt_specs = *this->specs(); format_specs fmt_specs = this->specs;
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
fmt_specs.type = 0;
this->write(value != 0);
} else if (std::is_same<T, char_type>::value) {
format_specs& fmt_specs = *this->specs();
if (fmt_specs.type && fmt_specs.type != 'c') if (fmt_specs.type && fmt_specs.type != 'c')
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
fmt_specs.sign = sign::none; fmt_specs.sign = sign::none;
@ -260,137 +258,48 @@ class printf_arg_formatter : public detail::arg_formatter_base<OutputIt, Char> {
// ignored for non-numeric types // ignored for non-numeric types
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
fmt_specs.align = align::right; fmt_specs.align = align::right;
return base::operator()(value); return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
} else {
return base::operator()(value);
} }
return this->out(); return base::operator()(value);
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
iterator operator()(T value) { OutputIt operator()(T value) {
return base::operator()(value); return base::operator()(value);
} }
/** Formats a null-terminated C string. */ /** Formats a null-terminated C string. */
iterator operator()(const char* value) { OutputIt operator()(const char* value) {
if (value) if (value) return base::operator()(value);
base::operator()(value); return write_null_pointer(this->specs.type != 'p');
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write("(null)");
return this->out();
} }
/** Formats a null-terminated wide C string. */ /** Formats a null-terminated wide C string. */
iterator operator()(const wchar_t* value) { OutputIt operator()(const wchar_t* value) {
if (value) if (value) return base::operator()(value);
base::operator()(value); return write_null_pointer(this->specs.type != 'p');
else if (this->specs()->type == 'p')
write_null_pointer(char_type());
else
this->write(L"(null)");
return this->out();
} }
iterator operator()(basic_string_view<char_type> value) { OutputIt operator()(basic_string_view<Char> value) {
return base::operator()(value); return base::operator()(value);
} }
iterator operator()(monostate value) { return base::operator()(value); }
/** Formats a pointer. */ /** Formats a pointer. */
iterator operator()(const void* value) { OutputIt operator()(const void* value) {
if (value) return base::operator()(value); return value ? base::operator()(value) : write_null_pointer();
this->specs()->type = 0;
write_null_pointer(char_type());
return this->out();
} }
/** Formats an argument of a custom (user-defined) type. */ /** Formats an argument of a custom (user-defined) type. */
iterator operator()(typename basic_format_arg<context_type>::handle handle) { OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
handle.format(context_.parse_context(), context_); auto parse_ctx =
return this->out(); basic_printf_parse_context<Char>(basic_string_view<Char>());
handle.format(parse_ctx, context_);
return this->out;
} }
}; };
template <typename T> struct printf_formatter { template <typename Char>
printf_formatter() = delete; void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
template <typename ParseContext>
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
detail::format_value(detail::get_container(ctx.out()), value);
return ctx.out();
}
};
/**
This template formats data and writes the output through an output iterator.
*/
template <typename OutputIt, typename Char> class basic_printf_context {
public:
/** The character type for the output. */
using char_type = Char;
using iterator = OutputIt;
using format_arg = basic_format_arg<basic_printf_context>;
using parse_context_type = basic_printf_parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
private:
using format_specs = basic_format_specs<char_type>;
OutputIt out_;
basic_format_args<basic_printf_context> args_;
parse_context_type parse_ctx_;
static void parse_flags(format_specs& specs, const Char*& it,
const Char* end);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
format_arg get_arg(int arg_index = -1);
// Parses argument index, flags and width and returns the argument index.
int parse_header(const Char*& it, const Char* end, format_specs& specs);
public:
/**
\rst
Constructs a ``printf_context`` object. References to the arguments are
stored in the context object so make sure they have appropriate lifetimes.
\endrst
*/
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
basic_format_args<basic_printf_context> args)
: out_(out), args_(args), parse_ctx_(format_str) {}
OutputIt out() { return out_; }
void advance_to(OutputIt it) { out_ = it; }
detail::locale_ref locale() { return {}; }
format_arg arg(int id) const { return args_.get(id); }
parse_context_type& parse_context() { return parse_ctx_; }
FMT_CONSTEXPR void on_error(const char* message) {
parse_ctx_.on_error(message);
}
/** Formats stored arguments and writes the output to the range. */
template <typename ArgFormatter = printf_arg_formatter<OutputIt, Char>>
OutputIt format();
};
template <typename OutputIt, typename Char>
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
const Char*& it,
const Char* end) { const Char* end) {
for (; it != end; ++it) { for (; it != end; ++it) {
switch (*it) { switch (*it) {
@ -417,35 +326,24 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
} }
} }
template <typename OutputIt, typename Char> template <typename Char, typename GetArg>
typename basic_printf_context<OutputIt, Char>::format_arg int parse_header(const Char*& it, const Char* end,
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) { basic_format_specs<Char>& specs, GetArg get_arg) {
if (arg_index < 0)
arg_index = parse_ctx_.next_arg_id();
else
parse_ctx_.check_arg_id(--arg_index);
return detail::get_arg(*this, arg_index);
}
template <typename OutputIt, typename Char>
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
const Char* end,
format_specs& specs) {
int arg_index = -1; int arg_index = -1;
char_type c = *it; Char c = *it;
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
// Parse an argument index (if followed by '$') or a width possibly // Parse an argument index (if followed by '$') or a width possibly
// preceded with '0' flag(s). // preceded with '0' flag(s).
detail::error_handler eh; int value = parse_nonnegative_int(it, end, -1);
int value = parse_nonnegative_int(it, end, eh);
if (it != end && *it == '$') { // value is an argument index if (it != end && *it == '$') { // value is an argument index
++it; ++it;
arg_index = value; arg_index = value != -1 ? value : max_value<int>();
} else { } else {
if (c == '0') specs.fill[0] = '0'; if (c == '0') specs.fill[0] = '0';
if (value != 0) { if (value != 0) {
// Nonzero value means that we parsed width and don't need to // Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now. // parse it or flags again, so return now.
if (value == -1) FMT_THROW(format_error("number is too big"));
specs.width = value; specs.width = value;
return arg_index; return arg_index;
} }
@ -455,58 +353,76 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
// Parse width. // Parse width.
if (it != end) { if (it != end) {
if (*it >= '0' && *it <= '9') { if (*it >= '0' && *it <= '9') {
detail::error_handler eh; specs.width = parse_nonnegative_int(it, end, -1);
specs.width = parse_nonnegative_int(it, end, eh); if (specs.width == -1) FMT_THROW(format_error("number is too big"));
} else if (*it == '*') { } else if (*it == '*') {
++it; ++it;
specs.width = static_cast<int>(visit_format_arg( specs.width = static_cast<int>(visit_format_arg(
detail::printf_width_handler<char_type>(specs), get_arg())); detail::printf_width_handler<Char>(specs), get_arg(-1)));
} }
} }
return arg_index; return arg_index;
} }
template <typename OutputIt, typename Char> template <typename Char, typename Context>
template <typename ArgFormatter> void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
OutputIt basic_printf_context<OutputIt, Char>::format() { basic_format_args<Context> args) {
auto out = this->out(); using OutputIt = buffer_appender<Char>;
const Char* start = parse_ctx_.begin(); auto out = OutputIt(buf);
const Char* end = parse_ctx_.end(); auto context = basic_printf_context<OutputIt, Char>(out, args);
auto parse_ctx = basic_printf_parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next
// argument.
auto get_arg = [&](int arg_index) {
if (arg_index < 0)
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
};
const Char* start = parse_ctx.begin();
const Char* end = parse_ctx.end();
auto it = start; auto it = start;
while (it != end) { while (it != end) {
char_type c = *it++; if (!detail::find<false, Char>(it, end, '%', it)) {
if (c != '%') continue; it = end; // detail::find leaves it == nullptr if it doesn't find '%'
break;
}
Char c = *it++;
if (it != end && *it == c) { if (it != end && *it == c) {
out = std::copy(start, it, out); out = detail::write(
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
start = ++it; start = ++it;
continue; continue;
} }
out = std::copy(start, it - 1, out); out = detail::write(out, basic_string_view<Char>(
start, detail::to_unsigned(it - 1 - start)));
format_specs specs; basic_format_specs<Char> specs;
specs.align = align::right; specs.align = align::right;
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs); int arg_index = parse_header(it, end, specs, get_arg);
if (arg_index == 0) on_error("argument not found"); if (arg_index == 0) parse_ctx.on_error("argument not found");
// Parse precision. // Parse precision.
if (it != end && *it == '.') { if (it != end && *it == '.') {
++it; ++it;
c = it != end ? *it : 0; c = it != end ? *it : 0;
if ('0' <= c && c <= '9') { if ('0' <= c && c <= '9') {
detail::error_handler eh; specs.precision = parse_nonnegative_int(it, end, 0);
specs.precision = parse_nonnegative_int(it, end, eh);
} else if (c == '*') { } else if (c == '*') {
++it; ++it;
specs.precision = static_cast<int>( specs.precision = static_cast<int>(
visit_format_arg(detail::printf_precision_handler(), get_arg())); visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
} else { } else {
specs.precision = 0; specs.precision = 0;
} }
} }
format_arg arg = get_arg(arg_index); auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is // For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored // specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) if (specs.precision >= 0 && arg.is_integral())
@ -516,9 +432,10 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
auto str = visit_format_arg(detail::get_cstring<Char>(), arg); auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
auto str_end = str + specs.precision; auto str_end = str + specs.precision;
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>( arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
str, basic_string_view<Char>(
detail::to_unsigned(nul != str_end ? nul - str : specs.precision))); str, detail::to_unsigned(nul != str_end ? nul - str
: specs.precision)));
} }
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg)) if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
specs.alt = false; specs.alt = false;
@ -532,7 +449,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
// Parse length and convert the argument to the required type. // Parse length and convert the argument to the required type.
c = it != end ? *it++ : 0; c = it != end ? *it++ : 0;
char_type t = it != end ? *it : 0; Char t = it != end ? *it : 0;
using detail::convert_arg; using detail::convert_arg;
switch (c) { switch (c) {
case 'h': case 'h':
@ -582,7 +499,8 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
specs.type = 'd'; specs.type = 'd';
break; break;
case 'c': case 'c':
visit_format_arg(detail::char_converter<basic_printf_context>(arg), visit_format_arg(
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
arg); arg);
break; break;
} }
@ -591,14 +509,16 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
start = it; start = it;
// Format argument. // Format argument.
out = visit_format_arg(ArgFormatter(out, specs, *this), arg); out = visit_format_arg(
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
} }
return std::copy(start, it, out); detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
} }
FMT_END_DETAIL_NAMESPACE
template <typename Char> template <typename Char>
using basic_printf_context_t = using basic_printf_context_t =
basic_printf_context<std::back_insert_iterator<detail::buffer<Char>>, Char>; basic_printf_context<detail::buffer_appender<Char>, Char>;
using printf_context = basic_printf_context_t<char>; using printf_context = basic_printf_context_t<char>;
using wprintf_context = basic_printf_context_t<wchar_t>; using wprintf_context = basic_printf_context_t<wchar_t>;
@ -612,9 +532,9 @@ using wprintf_args = basic_format_args<wprintf_context>;
arguments and can be implicitly converted to `~fmt::printf_args`. arguments and can be implicitly converted to `~fmt::printf_args`.
\endrst \endrst
*/ */
template <typename... Args> template <typename... T>
inline format_arg_store<printf_context, Args...> make_printf_args( inline auto make_printf_args(const T&... args)
const Args&... args) { -> format_arg_store<printf_context, T...> {
return {args...}; return {args...};
} }
@ -624,18 +544,19 @@ inline format_arg_store<printf_context, Args...> make_printf_args(
arguments and can be implicitly converted to `~fmt::wprintf_args`. arguments and can be implicitly converted to `~fmt::wprintf_args`.
\endrst \endrst
*/ */
template <typename... Args> template <typename... T>
inline format_arg_store<wprintf_context, Args...> make_wprintf_args( inline auto make_wprintf_args(const T&... args)
const Args&... args) { -> format_arg_store<wprintf_context, T...> {
return {args...}; return {args...};
} }
template <typename S, typename Char = char_t<S>> template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vsprintf( inline auto vsprintf(
const S& format, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args); vprintf(buffer, to_string_view(fmt), args);
return to_string(buffer); return to_string(buffer);
} }
@ -648,19 +569,20 @@ inline std::basic_string<Char> vsprintf(
std::string message = fmt::sprintf("The answer is %d", 42); std::string message = fmt::sprintf("The answer is %d", 42);
\endrst \endrst
*/ */
template <typename S, typename... Args, template <typename S, typename... T,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) { inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
using context = basic_printf_context_t<Char>; using context = basic_printf_context_t<Char>;
return vsprintf(to_string_view(format), make_format_args<context>(args...)); return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename S, typename Char = char_t<S>>
inline int vfprintf( inline auto vfprintf(
std::FILE* f, const S& format, std::FILE* f, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args); vprintf(buffer, to_string_view(fmt), args);
size_t size = buffer.size(); size_t size = buffer.size();
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
? -1 ? -1
@ -676,19 +598,19 @@ inline int vfprintf(
fmt::fprintf(stderr, "Don't %s!", "panic"); fmt::fprintf(stderr, "Don't %s!", "panic");
\endrst \endrst
*/ */
template <typename S, typename... Args, template <typename S, typename... T, typename Char = char_t<S>>
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>> inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
using context = basic_printf_context_t<Char>; using context = basic_printf_context_t<Char>;
return vfprintf(f, to_string_view(format), return vfprintf(f, to_string_view(fmt),
make_format_args<context>(args...)); fmt::make_format_args<context>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename S, typename Char = char_t<S>>
inline int vprintf( inline auto vprintf(
const S& format, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
return vfprintf(stdout, to_string_view(format), args); -> int {
return vfprintf(stdout, to_string_view(fmt), args);
} }
/** /**
@ -700,52 +622,31 @@ inline int vprintf(
fmt::printf("Elapsed time: %.2f seconds", 1.23); fmt::printf("Elapsed time: %.2f seconds", 1.23);
\endrst \endrst
*/ */
template <typename S, typename... Args, template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_ENABLE_IF(detail::is_string<S>::value)> inline auto printf(const S& fmt, const T&... args) -> int {
inline int printf(const S& format_str, const Args&... args) { return vprintf(
using context = basic_printf_context_t<char_t<S>>; to_string_view(fmt),
return vprintf(to_string_view(format_str), fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
make_format_args<context>(args...));
} }
template <typename S, typename Char = char_t<S>> template <typename S, typename Char = char_t<S>>
inline int vfprintf( FMT_DEPRECATED auto vfprintf(
std::basic_ostream<Char>& os, const S& format, std::basic_ostream<Char>& os, const S& fmt,
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) { basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
-> int {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
vprintf(buffer, to_string_view(format), args); vprintf(buffer, to_string_view(fmt), args);
detail::write_buffer(os, buffer); os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
return static_cast<int>(buffer.size()); return static_cast<int>(buffer.size());
} }
template <typename S, typename... T, typename Char = char_t<S>>
/** Formats arguments and writes the output to the range. */ FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
template <typename ArgFormatter, typename Char, const T&... args) -> int {
typename Context = return vfprintf(os, to_string_view(fmt),
basic_printf_context<typename ArgFormatter::iterator, Char>> fmt::make_format_args<basic_printf_context_t<Char>>(args...));
typename ArgFormatter::iterator vprintf(
detail::buffer<Char>& out, basic_string_view<Char> format_str,
basic_format_args<type_identity_t<Context>> args) {
typename ArgFormatter::iterator iter(out);
Context(iter, format_str, args).template format<ArgFormatter>();
return iter;
} }
/** FMT_MODULE_EXPORT_END
\rst
Prints formatted data to the stream *os*.
**Example**::
fmt::fprintf(cerr, "Don't %s!", "panic");
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
const Args&... args) {
using context = basic_printf_context_t<Char>;
return vfprintf(os, to_string_view(format_str),
make_format_args<context>(args...));
}
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_PRINTF_H_ #endif // FMT_PRINTF_H_

View File

@ -17,41 +17,31 @@
#include "format.h" #include "format.h"
// output only up to N items from the range.
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> struct formatting_base { template <typename Char, typename Enable = void> struct formatting_range {
#ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = '[';
Char postfix = ']';
#endif
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
}; };
template <typename Char, typename Enable = void> template <typename Char, typename Enable = void> struct formatting_tuple {
struct formatting_range : formatting_base<Char> { Char prefix = '(';
static FMT_CONSTEXPR_DECL const size_t range_length_limit = Char postfix = ')';
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
// range.
Char prefix;
Char delimiter;
Char postfix;
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
};
template <typename Char, typename Enable = void> template <typename ParseContext>
struct formatting_tuple : formatting_base<Char> { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
Char prefix; return ctx.begin();
Char delimiter; }
Char postfix;
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
}; };
namespace detail { namespace detail {
@ -75,8 +65,14 @@ OutputIterator copy(char ch, OutputIterator out) {
return out; return out;
} }
template <typename OutputIterator>
OutputIterator copy(wchar_t ch, OutputIterator out) {
*out++ = ch;
return out;
}
/// Return true value if T has std::string interface, like std::string_view. /// Return true value if T has std::string interface, like std::string_view.
template <typename T> class is_like_std_string { template <typename T> class is_std_string_like {
template <typename U> template <typename U>
static auto check(U* p) static auto check(U* p)
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); -> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
@ -88,19 +84,107 @@ template <typename T> class is_like_std_string {
}; };
template <typename Char> template <typename Char>
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
template <typename... Ts> struct conditional_helper {}; template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {}; template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VER || FMT_MSC_VER > 1800 #if !FMT_MSC_VER || FMT_MSC_VER > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload
template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
template <typename T, typename Enable = void>
struct has_member_fn_begin_end_t : std::false_type {};
template <typename T> template <typename T>
struct is_range_< struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
T, conditional_t<false, decltype(std::declval<T>().end())>>
conditional_helper<decltype(std::declval<T>().begin()), : std::true_type {};
decltype(std::declval<T>().end())>,
void>> : std::true_type {}; // Member function overload
template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
// ADL overload. Only participates in overload resolution if member functions
// are not found.
template <typename T>
auto range_begin(T&& rng)
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(begin(static_cast<T&&>(rng)))> {
return begin(static_cast<T&&>(rng));
}
template <typename T>
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
decltype(end(static_cast<T&&>(rng)))> {
return end(static_cast<T&&>(rng));
}
template <typename T, typename Enable = void>
struct has_const_begin_end : std::false_type {};
template <typename T, typename Enable = void>
struct has_mutable_begin_end : std::false_type {};
template <typename T>
struct has_const_begin_end<
T, void_t<decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>())),
decltype(detail::range_begin(
std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_begin(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};
template <typename T>
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
template <typename T, typename Enable = void> struct range_to_view;
template <typename T>
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
struct view_t {
const T* m_range_ptr;
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
};
static auto view(const T& range) -> view_t { return {&range}; }
};
template <typename T>
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
has_mutable_begin_end<T>::value>> {
struct view_t {
T m_range_copy;
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
};
static auto view(const T& range) -> view_t { return {range}; }
};
# undef FMT_DECLTYPE_RETURN
#endif #endif
/// tuple_size and tuple_element check. /// tuple_size and tuple_element check.
@ -157,30 +241,41 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
} }
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string< template <typename Range>
typename std::decay<Arg>::type>::value)> using value_type =
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
return add_space ? " {}" : "{}";
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
*out++ = ',';
*out++ = ' ';
return out;
} }
template <typename Arg, FMT_ENABLE_IF(is_like_std_string< template <
typename std::decay<Arg>::type>::value)> typename Char, typename OutputIt, typename Arg,
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
return add_space ? " \"{}\"" : "\"{}\""; OutputIt write_range_entry(OutputIt out, const Arg& v) {
*out++ = '"';
out = write<Char>(out, v);
*out++ = '"';
return out;
} }
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { template <typename Char, typename OutputIt, typename Arg,
return add_space ? " \"{}\"" : "\"{}\""; FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
} OutputIt write_range_entry(OutputIt out, const Arg v) {
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { *out++ = '\'';
return add_space ? L" \"{}\"" : L"\"{}\""; *out++ = v;
*out++ = '\'';
return out;
} }
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { template <
return add_space ? " '{}'" : "'{}'"; typename Char, typename OutputIt, typename Arg,
} FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { !std::is_same<Arg, Char>::value)>
return add_space ? L" '{}'" : L"'{}'"; OutputIt write_range_entry(OutputIt out, const Arg& v) {
return write<Char>(out, v);
} }
} // namespace detail } // namespace detail
@ -196,23 +291,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
// C++11 generic lambda for format() // C++11 generic lambda for format()
template <typename FormatContext> struct format_each { template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) { template <typename T> void operator()(const T& v) {
if (i > 0) { if (i > 0) out = detail::write_delimiter(out);
if (formatting.add_prepostfix_space) { out = detail::write_range_entry<Char>(out, v);
*out++ = ' ';
}
out = detail::copy(formatting.delimiter, out);
}
out = format_to(out,
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
++i; ++i;
} }
formatting_tuple<Char>& formatting; formatting_tuple<Char>& formatting;
size_t& i; size_t& i;
typename std::add_lvalue_reference<decltype( typename std::add_lvalue_reference<
std::declval<FormatContext>().out())>::type out; decltype(std::declval<FormatContext>().out())>::type out;
}; };
public: public:
@ -227,12 +313,9 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out(); auto out = ctx.out();
size_t i = 0; size_t i = 0;
detail::copy(formatting.prefix, out);
detail::copy(formatting.prefix, out);
detail::for_each(values, format_each<FormatContext>{formatting, i, out}); detail::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) {
*out++ = ' ';
}
detail::copy(formatting.postfix, out); detail::copy(formatting.postfix, out);
return ctx.out(); return ctx.out();
@ -241,14 +324,22 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value = static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_like_std_string<T>::value && detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value && !std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value; !std::is_constructible<detail::std_string_view<Char>, T>::value;
}; };
template <typename RangeT, typename Char> template <typename T, typename Char>
struct formatter<RangeT, Char, struct formatter<
enable_if_t<fmt::is_range<RangeT, Char>::value>> { T, Char,
enable_if_t<
fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
&& (has_formatter<detail::value_type<T>, format_context>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif
>> {
formatting_range<Char> formatting; formatting_range<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
@ -257,75 +348,67 @@ struct formatter<RangeT, Char,
} }
template <typename FormatContext> template <typename FormatContext>
typename FormatContext::iterator format(const RangeT& values, typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
FormatContext& ctx) {
auto out = detail::copy(formatting.prefix, ctx.out()); auto out = detail::copy(formatting.prefix, ctx.out());
size_t i = 0; size_t i = 0;
auto it = values.begin(); auto view = detail::range_to_view<T>::view(values);
auto end = values.end(); auto it = view.begin();
auto end = view.end();
for (; it != end; ++it) { for (; it != end; ++it) {
if (i > 0) { if (i > 0) out = detail::write_delimiter(out);
if (formatting.add_prepostfix_space) *out++ = ' '; out = detail::write_range_entry<Char>(out, *it);
out = detail::copy(formatting.delimiter, out); ++i;
} }
out = format_to(out,
detail::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
out = format_to(out, " ... <other elements>");
break;
}
}
if (formatting.add_prepostfix_space) *out++ = ' ';
return detail::copy(formatting.postfix, out); return detail::copy(formatting.postfix, out);
} }
}; };
template <typename Char, typename... T> struct tuple_arg_join : detail::view { template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple; const std::tuple<T...>& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s) tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
: tuple{t}, sep{s} {} : tuple(t), sep{s} {}
}; };
template <typename Char, typename... T> template <typename Char, typename... T>
struct formatter<tuple_arg_join<Char, T...>, Char> { using tuple_arg_join = tuple_join_view<Char, T...>;
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
template <typename FormatContext> template <typename FormatContext>
typename FormatContext::iterator format( auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) { typename FormatContext::iterator {
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{}); return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
} }
private: private:
template <typename FormatContext, size_t... N> template <typename FormatContext, size_t... N>
typename FormatContext::iterator format( auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
const tuple_arg_join<Char, T...>& value, FormatContext& ctx, detail::index_sequence<N...>) ->
detail::index_sequence<N...>) { typename FormatContext::iterator {
return format_args(value, ctx, std::get<N>(value.tuple)...); return format_args(value, ctx, std::get<N>(value.tuple)...);
} }
template <typename FormatContext> template <typename FormatContext>
typename FormatContext::iterator format_args( auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
const tuple_arg_join<Char, T...>&, FormatContext& ctx) { typename FormatContext::iterator {
// NOTE: for compilers that support C++17, this empty function instantiation // NOTE: for compilers that support C++17, this empty function instantiation
// can be replaced with a constexpr branch in the variadic overload. // can be replaced with a constexpr branch in the variadic overload.
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, typename Arg, typename... Args> template <typename FormatContext, typename Arg, typename... Args>
typename FormatContext::iterator format_args( auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
const tuple_arg_join<Char, T...>& value, FormatContext& ctx, const Arg& arg, const Args&... args) ->
const Arg& arg, const Args&... args) { typename FormatContext::iterator {
using base = formatter<typename std::decay<Arg>::type, Char>; using base = formatter<typename std::decay<Arg>::type, Char>;
auto out = ctx.out(); auto out = base().format(arg, ctx);
out = base{}.format(arg, ctx);
if (sizeof...(Args) > 0) { if (sizeof...(Args) > 0) {
out = std::copy(value.sep.begin(), value.sep.end(), out); out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out); ctx.advance_to(out);
@ -335,6 +418,8 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
} }
}; };
FMT_MODULE_EXPORT_BEGIN
/** /**
\rst \rst
Returns an object that formats `tuple` with elements separated by `sep`. Returns an object that formats `tuple` with elements separated by `sep`.
@ -347,14 +432,15 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
\endrst \endrst
*/ */
template <typename... T> template <typename... T>
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple, FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
string_view sep) { -> tuple_join_view<char, T...> {
return {tuple, sep}; return {tuple, sep};
} }
template <typename... T> template <typename... T>
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple, FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
wstring_view sep) { basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> {
return {tuple, sep}; return {tuple, sep};
} }
@ -370,17 +456,12 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
\endrst \endrst
*/ */
template <typename T> template <typename T>
arg_join<const T*, const T*, char> join(std::initializer_list<T> list, auto join(std::initializer_list<T> list, string_view sep)
string_view sep) { -> join_view<const T*, const T*> {
return join(std::begin(list), std::end(list), sep);
}
template <typename T>
arg_join<const T*, const T*, wchar_t> join(std::initializer_list<T> list,
wstring_view sep) {
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_RANGES_H_ #endif // FMT_RANGES_H_

View File

@ -0,0 +1,236 @@
// Formatting library for C++ - optional wchar_t and exotic character support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_WCHAR_H_
#define FMT_WCHAR_H_
#include <cwchar>
#include <tuple>
#include "format.h"
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
}
FMT_MODULE_EXPORT_BEGIN
using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>;
using wformat_context = buffer_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>;
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;
#else
template <typename... Args>
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
#endif
template <> struct is_char<wchar_t> : std::true_type {};
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) {
return {args...};
}
inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
return {s};
}
#endif
} // namespace literals
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep)
-> join_view<It, Sentinel, wchar_t> {
return {begin, end, sep};
}
template <typename Range>
auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
wchar_t> {
return join(std::begin(range), std::end(range), sep);
}
template <typename T>
auto join(std::initializer_list<T> list, wstring_view sep)
-> join_view<const T*, const T*, wchar_t> {
return join(std::begin(list), std::end(list), sep);
}
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);
}
// 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> {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat(to_string_view(format_str), vargs);
}
template <typename Locale, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat(
const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> std::basic_string<Char> {
return detail::vformat(loc, to_string_view(format_str), args);
}
template <typename Locale, typename S, typename... Args,
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)
-> std::basic_string<Char> {
return detail::vformat(loc, to_string_view(format_str),
fmt::make_args_checked<Args...>(format_str, args...));
}
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, to_string_view(format_str), args);
return detail::get_iterator(buf);
}
template <typename OutputIt, typename S, typename... Args,
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 {
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
return vformat_to(out, to_string_view(fmt), vargs);
}
template <typename S, typename... Args, typename Char, size_t SIZE,
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
const S& format_str, Args&&... args) ->
typename buffer_context<Char>::iterator {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
detail::vformat_to(buf, to_string_view(format_str), vargs);
return detail::buffer_appender<Char>(buf);
}
template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(
OutputIt out, const Locale& loc, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
return detail::get_iterator(buf);
}
template <
typename OutputIt, typename Locale, typename S, typename... Args,
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) ->
typename std::enable_if<enable, OutputIt>::type {
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, loc, to_string_view(format_str), vargs);
}
template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(
OutputIt out, size_t n, basic_string_view<Char> 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);
detail::vformat_to(buf, format_str, args);
return {buf.out(), buf.count()};
}
template <typename OutputIt, typename S, typename... Args,
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> {
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
return vformat_to_n(out, n, to_string_view(fmt), vargs);
}
template <typename S, typename... Args, 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;
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
detail::vformat_to(buf, to_string_view(fmt), vargs);
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)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
inline void vprint(wstring_view fmt, wformat_args args) {
vprint(stdout, fmt, args);
}
template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
}
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(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_NAMESPACE
#endif // FMT_WCHAR_H_

100
externals/dynarmic/externals/fmt/src/fmt.cc vendored Executable file
View File

@ -0,0 +1,100 @@
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
#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 <functional>
#include <iterator>
#include <limits>
#include <locale>
#include <memory>
#include <ostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
#if _MSC_VER
# include <intrin.h>
#endif
#if defined __APPLE__ || defined(__FreeBSD__)
# include <xlocale.h>
#endif
#if __has_include(<winapifamily.h>)
# include <winapifamily.h>
#endif
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h>
# include <sys/stat.h>
# include <sys/types.h>
# ifndef _WIN32
# include <unistd.h>
# else
# include <io.h>
# endif
#endif
#ifdef _WIN32
# 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
#include "fmt/args.h"
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/compile.h"
#include "fmt/format.h"
#include "fmt/os.h"
#include "fmt/printf.h"
#include "fmt/xchar.h"
// gcc doesn't yet implement private module fragments
#if !FMT_GCC_VERSION
module : private;
#endif
#include "format.cc"
#include "os.cc"

View File

@ -23,9 +23,12 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value) return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value); : snprintf_ptr(buf, size, format, precision, value);
} }
} // namespace detail
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>; template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
FMT_NOEXCEPT;
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
FMT_NOEXCEPT;
} // namespace detail
// Workaround a bug in MSVC2013 that prevents instantiation of format_float. // Workaround a bug in MSVC2013 that prevents instantiation of format_float.
int (*instantiate_format_float)(double, int, detail::float_specs, int (*instantiate_format_float)(double, int, detail::float_specs,
@ -38,15 +41,15 @@ template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API std::string detail::grouping_impl<char>(locale_ref); template FMT_API auto detail::thousands_sep_impl(locale_ref)
template FMT_API char detail::thousands_sep_impl(locale_ref); -> thousands_sep_result<char>;
template FMT_API char detail::decimal_point_impl(locale_ref); template FMT_API char detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<char>::append(const char*, const char*); template FMT_API void detail::buffer<char>::append(const char*, const char*);
template FMT_API FMT_BUFFER_CONTEXT(char)::iterator detail::vformat_to( template FMT_API void detail::vformat_to(
detail::buffer<char>&, string_view, detail::buffer<char>&, string_view,
basic_format_args<FMT_BUFFER_CONTEXT(char)>); basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
template FMT_API int detail::snprintf_float(double, int, detail::float_specs, template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
detail::buffer<char>&); detail::buffer<char>&);
@ -60,10 +63,13 @@ template FMT_API int detail::format_float(long double, int, detail::float_specs,
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref); template FMT_API auto detail::thousands_sep_impl(locale_ref)
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref); -> thousands_sep_result<wchar_t>;
template FMT_API wchar_t detail::decimal_point_impl(locale_ref); template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*, template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
const wchar_t*); const wchar_t*);
template struct detail::basic_data<void>;
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -25,7 +25,6 @@
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
# endif # endif
# include <io.h> # include <io.h>
# include <windows.h>
# define O_CREAT _O_CREAT # define O_CREAT _O_CREAT
# define O_TRUNC _O_TRUNC # define O_TRUNC _O_TRUNC
@ -55,16 +54,16 @@
namespace { namespace {
#ifdef _WIN32 #ifdef _WIN32
// Return type of read and write functions. // Return type of read and write functions.
using RWResult = int; using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert // On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow. // it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) { inline unsigned convert_rwcount(std::size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX; return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
} }
#else #elif FMT_USE_FCNTL
// Return type of read and write functions. // Return type of read and write functions.
using RWResult = ssize_t; using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; } inline std::size_t convert_rwcount(std::size_t count) { return count; }
#endif #endif
@ -73,14 +72,14 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#ifdef _WIN32 #ifdef _WIN32
detail::utf16_to_utf8::utf16_to_utf8(wstring_view s) { detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
if (int error_code = convert(s)) { if (int error_code = convert(s)) {
FMT_THROW(windows_error(error_code, FMT_THROW(windows_error(error_code,
"cannot convert string from UTF-16 to UTF-8")); "cannot convert string from UTF-16 to UTF-8"));
} }
} }
int detail::utf16_to_utf8::convert(wstring_view s) { int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size()); int s_size = static_cast<int>(s.size());
if (s_size == 0) { if (s_size == 0) {
@ -101,45 +100,85 @@ int detail::utf16_to_utf8::convert(wstring_view s) {
return 0; return 0;
} }
void windows_error::init(int err_code, string_view format_str, namespace detail {
class system_message {
system_message(const system_message&) = delete;
void operator=(const system_message&) = delete;
unsigned long result_;
wchar_t* message_;
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
}
public:
explicit system_message(unsigned long error_code)
: result_(0), message_(nullptr) {
result_ = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
if (result_ != 0) {
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
--result_;
}
}
}
~system_message() { LocalFree(message_); }
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
return basic_string_view<wchar_t>(message_, result_);
}
};
class utf8_system_category final : public std::error_category {
public:
const char* name() const FMT_NOEXCEPT override { return "system"; }
std::string message(int error_code) const override {
system_message msg(error_code);
if (msg) {
utf16_to_utf8 utf8_message;
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
return utf8_message.str();
}
}
return "unknown error";
}
};
} // namespace detail
FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
static const detail::utf8_system_category category;
return category;
}
std::system_error vwindows_error(int err_code, string_view format_str,
format_args args) { format_args args) {
error_code_ = err_code; auto ec = std::error_code(err_code, system_category());
memory_buffer buffer; return std::system_error(ec, vformat(format_str, args));
detail::format_windows_error(buffer, err_code, vformat(format_str, args));
std::runtime_error& base = *this;
base = std::runtime_error(to_string(buffer));
} }
void detail::format_windows_error(detail::buffer<char>& out, int error_code, void detail::format_windows_error(detail::buffer<char>& out, int error_code,
string_view message) FMT_NOEXCEPT { const char* message) FMT_NOEXCEPT {
FMT_TRY { FMT_TRY {
wmemory_buffer buf; system_message msg(error_code);
buf.resize(inline_buffer_size); if (msg) {
for (;;) {
wchar_t* system_message = &buf[0];
int result = FormatMessageW(
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), system_message,
static_cast<uint32_t>(buf.size()), nullptr);
if (result != 0) {
utf16_to_utf8 utf8_message; utf16_to_utf8 utf8_message;
if (utf8_message.convert(system_message) == ERROR_SUCCESS) { if (utf8_message.convert(msg) == ERROR_SUCCESS) {
format_to(std::back_inserter(out), "{}: {}", message, utf8_message); format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
return; return;
} }
break;
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
break; // Can't get error message, report error code instead.
buf.resize(buf.size() * 2);
} }
} }
FMT_CATCH(...) {} FMT_CATCH(...) {}
format_error_code(out, error_code, message); format_error_code(out, error_code, message);
} }
void report_windows_error(int error_code, void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
fmt::string_view message) FMT_NOEXCEPT {
report_error(detail::format_windows_error, error_code, message); report_error(detail::format_windows_error, error_code, message);
} }
#endif // _WIN32 #endif // _WIN32
@ -228,14 +267,14 @@ long long file::size() const {
} }
std::size_t file::read(void* buffer, std::size_t count) { std::size_t file::read(void* buffer, std::size_t count) {
RWResult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file")); if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
return detail::to_unsigned(result); return detail::to_unsigned(result);
} }
std::size_t file::write(const void* buffer, std::size_t count) { std::size_t file::write(const void* buffer, std::size_t count) {
RWResult result = 0; rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file")); if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
return detail::to_unsigned(result); return detail::to_unsigned(result);
@ -259,10 +298,10 @@ void file::dup2(int fd) {
} }
} }
void file::dup2(int fd, error_code& ec) FMT_NOEXCEPT { void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
int result = 0; int result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
if (result == -1) ec = error_code(errno); if (result == -1) ec = std::error_code(errno, std::generic_category());
} }
void file::pipe(file& read_end, file& write_end) { void file::pipe(file& read_end, file& write_end) {
@ -313,5 +352,9 @@ long getpagesize() {
return size; return size;
# endif # endif
} }
FMT_API void ostream::grow(size_t) {
if (this->size() == this->capacity()) flush();
}
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -2,5 +2,3 @@ This directory contains build support files such as
* CMake modules * CMake modules
* Build scripts * Build scripts
* qmake (static build with dynamic libc only)

View File

@ -6,11 +6,8 @@ clone_depth: 1
image: image:
- Visual Studio 2015 - Visual Studio 2015
- Visual Studio 2019
- Visual Studio 2017
platform: platform:
- Win32
- x64 - x64
environment: environment:
@ -18,13 +15,6 @@ environment:
MSVC_DEFAULT_OPTIONS: ON MSVC_DEFAULT_OPTIONS: ON
BUILD: msvc BUILD: msvc
matrix:
exclude:
- image: Visual Studio 2015
platform: Win32
- image: Visual Studio 2019
platform: Win32
before_build: before_build:
- mkdir build - mkdir build
- cd build - cd build

View File

@ -0,0 +1,58 @@
#!/usr/bin/env python
# Build the documentation in CI.
from __future__ import print_function
import errno, os, shutil, subprocess, sys, urllib
from subprocess import call, check_call, Popen, PIPE, STDOUT
def rmtree_if_exists(dir):
try:
shutil.rmtree(dir)
except OSError as e:
if e.errno == errno.ENOENT:
pass
# Build the docs.
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
import build
build.create_build_env()
html_dir = build.build_docs()
repo = 'fmtlib.github.io'
branch = os.environ['GITHUB_REF']
is_ci = 'CI' in os.environ
if is_ci and branch != 'refs/heads/master':
print('Branch: ' + branch)
exit(0) # Ignore non-master branches
if is_ci and 'KEY' not in os.environ:
# Don't update the repo if building in CI from an account that doesn't have
# push access.
print('Skipping update of ' + repo)
exit(0)
# Clone the fmtlib.github.io repo.
rmtree_if_exists(repo)
git_url = 'https://github.com/' if is_ci else 'git@github.com:'
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
# Copy docs to the repo.
target_dir = os.path.join(repo, 'dev')
rmtree_if_exists(target_dir)
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
if is_ci:
check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
# Push docs to GitHub pages.
check_call(['git', 'add', '--all'], cwd=repo)
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
cmd = 'git push'
if is_ci:
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
# Print the output without the key.
print(p.communicate()[0].decode('utf-8').replace(os.environ['KEY'], '$KEY'))
if p.returncode != 0:
raise subprocess.CalledProcessError(p.returncode, cmd)

View File

@ -1,3 +1,4 @@
import java.nio.file.Paths
// General gradle arguments for root project // General gradle arguments for root project
buildscript { buildscript {
@ -7,14 +8,12 @@ buildscript {
} }
dependencies { dependencies {
// //
// https://developer.android.com/studio/releases/gradle-plugin // https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
// //
// Notice that 3.3.0 here is the version of [Android Gradle Plugin] // Notice that 4.0.0 here is the version of [Android Gradle Plugin]
// Accroding to URL above you will need Gradle 5.0 or higher // Accroding to URL above you will need Gradle 6.1 or higher
// //
// If you are using Android Studio, and it is using Gradle's lower classpath "com.android.tools.build:gradle:4.1.1"
// version, Use the plugin version 3.1.3 ~ 3.2.0 for Gradle 4.4 ~ 4.10
classpath 'com.android.tools.build:gradle:3.3.0'
} }
} }
repositories { repositories {
@ -22,9 +21,12 @@ repositories {
jcenter() jcenter()
} }
// Output: Shared library (.so) for Android // Project's root where CMakeLists.txt exists: rootDir/support/.cxx -> rootDir
apply plugin: 'com.android.library' def rootDir = Paths.get(project.buildDir.getParent()).getParent()
println("rootDir: ${rootDir}")
// Output: Shared library (.so) for Android
apply plugin: "com.android.library"
android { android {
compileSdkVersion 25 // Android 7.0 compileSdkVersion 25 // Android 7.0
@ -41,13 +43,13 @@ android {
include "arm64-v8a", "armeabi-v7a", "x86_64" include "arm64-v8a", "armeabi-v7a", "x86_64"
} }
} }
ndkVersion "21.3.6528147" // ANDROID_NDK_HOME is deprecated. Be explicit
defaultConfig { defaultConfig {
minSdkVersion 21 // Android 5.0+ minSdkVersion 21 // Android 5.0+
targetSdkVersion 25 // Follow Compile SDK targetSdkVersion 25 // Follow Compile SDK
versionCode 21 // Follow release count versionCode 34 // Follow release count
versionName "5.3.0" // Follow Official version versionName "7.1.2" // Follow Official version
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@ -56,9 +58,9 @@ android {
arguments "-DFMT_TEST=false" // Skip test arguments "-DFMT_TEST=false" // Skip test
arguments "-DFMT_DOC=false" // Skip document arguments "-DFMT_DOC=false" // Skip document
cppFlags "-std=c++17" cppFlags "-std=c++17"
targets "fmt"
} }
} }
println("Gradle CMake Plugin: ")
println(externalNativeBuild.cmake.cppFlags) println(externalNativeBuild.cmake.cppFlags)
println(externalNativeBuild.cmake.arguments) println(externalNativeBuild.cmake.arguments)
} }
@ -69,16 +71,27 @@ android {
// neighbor of the top level cmake // neighbor of the top level cmake
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path "../CMakeLists.txt" version "3.10.0+"
path "${rootDir}/CMakeLists.txt"
// buildStagingDirectory "./build" // Custom path for cmake output // buildStagingDirectory "./build" // Custom path for cmake output
} }
//println(cmake.path)
} }
sourceSets{ sourceSets{
// Android Manifest for Gradle // Android Manifest for Gradle
main { main {
manifest.srcFile 'AndroidManifest.xml' manifest.srcFile "AndroidManifest.xml"
}
}
// https://developer.android.com/studio/build/native-dependencies#build_system_configuration
buildFeatures {
prefab true
prefabPublishing true
}
prefab {
fmt {
headers "${rootDir}/include"
} }
} }
} }
@ -88,20 +101,32 @@ assemble.doLast
// Instead of `ninja install`, Gradle will deploy the files. // Instead of `ninja install`, Gradle will deploy the files.
// We are doing this since FMT is dependent to the ANDROID_STL after build // We are doing this since FMT is dependent to the ANDROID_STL after build
copy { copy {
from 'build/intermediates/cmake' from "build/intermediates/cmake"
into '../libs' into "${rootDir}/libs"
} }
// Copy debug binaries // Copy debug binaries
copy { copy {
from '../libs/debug/obj' from "${rootDir}/libs/debug/obj"
into '../libs/debug' into "${rootDir}/libs/debug"
} }
// Copy Release binaries // Copy Release binaries
copy { copy {
from '../libs/release/obj' from "${rootDir}/libs/release/obj"
into '../libs/release' into "${rootDir}/libs/release"
} }
// Remove empty directory // Remove empty directory
delete '../libs/debug/obj' delete "${rootDir}/libs/debug/obj"
delete '../libs/release/obj' delete "${rootDir}/libs/release/obj"
// Copy AAR files. Notice that the aar is named after the folder of this script.
copy {
from "build/outputs/aar/support-release.aar"
into "${rootDir}/libs"
rename "support-release.aar", "fmt-release.aar"
}
copy {
from "build/outputs/aar/support-debug.aar"
into "${rootDir}/libs"
rename "support-debug.aar", "fmt-debug.aar"
}
} }

View File

@ -65,7 +65,7 @@ class Translator(nodes.NodeVisitor):
self.write('\n\n') self.write('\n\n')
def visit_paragraph(self, node): def visit_paragraph(self, node):
pass self.write('\n\n')
def depart_paragraph(self, node): def depart_paragraph(self, node):
pass pass

View File

@ -1,52 +1,10 @@
#------------------------------------------------------------------------------ add_subdirectory(gtest)
# Build the google test library
# We compile Google Test ourselves instead of using pre-compiled libraries.
# See the Google Test FAQ "Why is it not recommended to install a
# pre-compiled copy of Google Test (for example, into /usr/local)?"
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
add_library(gmock STATIC
gmock-gtest-all.cc gmock/gmock.h gtest/gtest.h gtest/gtest-spi.h)
target_compile_definitions(gmock PUBLIC GTEST_HAS_STD_WSTRING=1)
target_include_directories(gmock SYSTEM PUBLIC . gmock gtest)
find_package(Threads)
if (Threads_FOUND)
target_link_libraries(gmock ${CMAKE_THREAD_LIBS_INIT})
else ()
target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0)
endif ()
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
if (MSVC)
# Workaround a bug in implementation of variadic templates in MSVC11.
target_compile_definitions(gmock PUBLIC _VARIADIC_MAX=10)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gmock PRIVATE _CRT_SECURE_NO_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Disable MSVC warnings of POSIX functions.
target_compile_options(gmock PUBLIC -Wno-deprecated-declarations)
endif ()
endif ()
# GTest doesn't detect <tuple> with clang.
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
target_compile_definitions(gmock PUBLIC GTEST_USE_OWN_TR1_TUPLE=1)
endif ()
# Silence MSVC tr1 deprecation warning in gmock.
target_compile_definitions(gmock
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)
#------------------------------------------------------------------------------
# Build the actual library tests
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc) set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
add_library(test-main STATIC ${TEST_MAIN_SRC}) add_library(test-main STATIC ${TEST_MAIN_SRC})
target_include_directories(test-main SYSTEM PUBLIC gtest gmock) target_include_directories(test-main PUBLIC
target_link_libraries(test-main gmock fmt) $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
target_link_libraries(test-main gtest)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)
@ -73,8 +31,23 @@ endfunction()
# Adds a test. # Adds a test.
# Usage: add_fmt_test(name srcs...) # Usage: add_fmt_test(name srcs...)
function(add_fmt_test name) function(add_fmt_test name)
add_fmt_executable(${name} ${name}.cc ${ARGN}) cmake_parse_arguments(ADD_FMT_TEST "HEADER_ONLY;MODULE" "" "" ${ARGN})
target_link_libraries(${name} test-main)
set(sources ${name}.cc ${ADD_FMT_TEST_UNPARSED_ARGUMENTS})
if (ADD_FMT_TEST_HEADER_ONLY)
set(sources ${sources} ${TEST_MAIN_SRC} ../src/os.cc)
set(libs gtest fmt-header-only)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wno-weak-vtables)
endif ()
elseif (ADD_FMT_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)
endif ()
add_fmt_executable(${name} ${sources})
target_link_libraries(${name} ${libs})
# Define if certain C++ features can be used. # Define if certain C++ features can be used.
if (FMT_PEDANTIC) if (FMT_PEDANTIC)
@ -83,39 +56,71 @@ function(add_fmt_test name)
if (FMT_WERROR) if (FMT_WERROR)
target_compile_options(${name} PRIVATE ${WERROR_FLAG}) target_compile_options(${name} PRIVATE ${WERROR_FLAG})
endif () endif ()
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
add_test(NAME ${name} COMMAND ${name}) add_test(NAME ${name} COMMAND ${name})
endfunction() endfunction()
add_fmt_test(args-test)
add_fmt_test(assert-test) add_fmt_test(assert-test)
add_fmt_test(chrono-test) add_fmt_test(chrono-test)
add_fmt_test(color-test) add_fmt_test(color-test)
add_fmt_test(core-test) add_fmt_test(core-test)
add_fmt_test(grisu-test)
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
add_fmt_test(gtest-extra-test) add_fmt_test(gtest-extra-test)
add_fmt_test(format-test mock-allocator.h) add_fmt_test(format-test mock-allocator.h)
if (MSVC) if (MSVC)
target_compile_options(format-test PRIVATE /bigobj) target_compile_options(format-test PRIVATE /bigobj)
endif () endif ()
if (NOT (MSVC AND BUILD_SHARED_LIBS)) if (NOT (MSVC AND BUILD_SHARED_LIBS))
add_fmt_test(format-impl-test) add_fmt_test(format-impl-test HEADER_ONLY header-only-test.cc)
endif () endif ()
add_fmt_test(locale-test)
add_fmt_test(ostream-test) add_fmt_test(ostream-test)
add_fmt_test(compile-test) add_fmt_test(compile-test)
add_fmt_test(printf-test) add_fmt_test(printf-test)
add_fmt_test(custom-formatter-test)
add_fmt_test(ranges-test) add_fmt_test(ranges-test)
add_fmt_test(scan-test) add_fmt_test(scan-test)
add_fmt_test(unicode-test HEADER_ONLY)
if (MSVC)
target_compile_options(unicode-test PRIVATE /utf-8)
endif ()
add_fmt_test(xchar-test)
add_fmt_test(enforce-checks-test)
target_compile_definitions(enforce-checks-test PRIVATE
-DFMT_ENFORCE_COMPILE_STRING)
if (NOT MSVC_BUILD_STATIC) if (FMT_CAN_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_include_directories(test-module PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
enable_module(test-module)
add_fmt_test(module-test MODULE)
if (MSVC)
target_compile_options(test-module PRIVATE /utf-8)
target_compile_options(module-test PRIVATE /utf-8)
endif ()
endif ()
if (NOT DEFINED MSVC_STATIC_RUNTIME AND MSVC)
foreach (flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if (${flag_var} MATCHES "^(/|-)(MT|MTd)")
set(MSVC_STATIC_RUNTIME ON)
break()
endif()
endforeach()
endif()
if (NOT MSVC_STATIC_RUNTIME)
add_fmt_executable(posix-mock-test add_fmt_executable(posix-mock-test
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
target_include_directories( target_include_directories(
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include) posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(posix-mock-test gmock) target_link_libraries(posix-mock-test gtest)
target_include_directories(posix-mock-test SYSTEM PUBLIC gtest gmock)
if (FMT_PEDANTIC) if (FMT_PEDANTIC)
target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS}) target_compile_options(posix-mock-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
endif () endif ()
@ -126,21 +131,9 @@ if (NOT MSVC_BUILD_STATIC)
add_fmt_test(os-test) add_fmt_test(os-test)
endif () endif ()
add_fmt_executable(header-only-test
header-only-test.cc header-only-test2.cc test-main.cc)
target_link_libraries(header-only-test gmock)
target_include_directories(header-only-test SYSTEM PUBLIC gtest gmock)
if (TARGET fmt-header-only)
target_link_libraries(header-only-test fmt-header-only)
else ()
target_include_directories(
header-only-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
target_compile_definitions(header-only-test PRIVATE FMT_HEADER_ONLY=1)
endif ()
message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}") message(STATUS "FMT_PEDANTIC: ${FMT_PEDANTIC}")
if (FMT_PEDANTIC) if (FMT_PEDANTIC AND CXX_STANDARD LESS 20)
# MSVC fails to compile GMock when C++17 is enabled. # MSVC fails to compile GMock when C++17 is enabled.
if (FMT_HAS_VARIANT AND NOT MSVC) if (FMT_HAS_VARIANT AND NOT MSVC)
add_fmt_test(std-format-test) add_fmt_test(std-format-test)
@ -195,6 +188,7 @@ if (FMT_PEDANTIC AND NOT WIN32)
--build-makeprogram ${CMAKE_MAKE_PROGRAM} --build-makeprogram ${CMAKE_MAKE_PROGRAM}
--build-options --build-options
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DFMT_DIR=${PROJECT_BINARY_DIR}" "-DFMT_DIR=${PROJECT_BINARY_DIR}"
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}" "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
@ -215,6 +209,21 @@ if (FMT_PEDANTIC AND NOT WIN32)
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif () endif ()
# This test are disabled on Windows because it is only *NIX issue.
if (FMT_PEDANTIC AND NOT WIN32)
add_test(static-export-test ${CMAKE_CTEST_COMMAND}
-C ${CMAKE_BUILD_TYPE}
--build-and-test
"${CMAKE_CURRENT_SOURCE_DIR}/static-export-test"
"${CMAKE_CURRENT_BINARY_DIR}/static-export-test"
--build-generator ${CMAKE_GENERATOR}
--build-makeprogram ${CMAKE_MAKE_PROGRAM}
--build-options
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}"
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
endif ()
# Activate optional CUDA tests if CUDA is found. For version selection see # Activate optional CUDA tests if CUDA is found. For version selection see
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features # https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
if (FMT_CUDA_TEST) if (FMT_CUDA_TEST)

View File

@ -1,17 +1,17 @@
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.1...3.18)
project(fmt-test) project(fmt-test CXX)
add_subdirectory(../.. fmt) add_subdirectory(../.. fmt)
add_executable(library-test "main.cc") add_executable(library-test main.cc)
target_link_libraries(library-test fmt::fmt)
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
target_include_directories(library-test PUBLIC SYSTEM .) target_include_directories(library-test PUBLIC SYSTEM .)
target_compile_options(library-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
target_link_libraries(library-test fmt::fmt)
if (TARGET fmt::fmt-header-only) if (TARGET fmt::fmt-header-only)
add_executable(header-only-test "main.cc") add_executable(header-only-test main.cc)
target_link_libraries(header-only-test fmt::fmt-header-only)
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
target_include_directories(header-only-test PUBLIC SYSTEM .) target_include_directories(header-only-test PUBLIC SYSTEM .)
target_compile_options(header-only-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
target_link_libraries(header-only-test fmt::fmt-header-only)
endif () endif ()

View File

@ -1,6 +1,5 @@
#include "fmt/format.h" #include "fmt/core.h"
int main(int argc, char** argv) { int main(int argc, char** argv) {
for(int i = 0; i < argc; ++i) for (int i = 0; i < argc; ++i) fmt::print("{}: {}\n", i, argv[i]);
fmt::print("{}: {}\n", i, argv[i]);
} }

View File

@ -0,0 +1,173 @@
// Formatting library for C++ - dynamic argument store tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/args.h"
#include "gtest/gtest.h"
TEST(args_test, basic) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
store.push_back(42);
store.push_back("abc1");
store.push_back(1.5f);
EXPECT_EQ("42 and abc1 and 1.5", fmt::vformat("{} and {} and {}", store));
}
TEST(args_test, strings_and_refs) {
// Unfortunately the tests are compiled with old ABI so strings use COW.
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
char str[] = "1234567890";
store.push_back(str);
store.push_back(std::cref(str));
store.push_back(fmt::string_view{str});
str[0] = 'X';
auto result = fmt::vformat("{} and {} and {}", store);
EXPECT_EQ("1234567890 and X234567890 and X234567890", result);
}
struct custom_type {
int i = 0;
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<custom_type> {
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const custom_type& p, FormatContext& ctx) -> decltype(ctx.out()) {
return format_to(ctx.out(), "cust={}", p.i);
}
};
FMT_END_NAMESPACE
TEST(args_test, custom_format) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
auto c = custom_type();
store.push_back(c);
++c.i;
store.push_back(c);
++c.i;
store.push_back(std::cref(c));
++c.i;
auto result = fmt::vformat("{} and {} and {}", store);
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
}
struct to_stringable {
friend fmt::string_view to_string_view(to_stringable) { return {}; }
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<to_stringable> {
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(to_stringable, format_context& ctx) -> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(args_test, to_string_and_formatter) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
auto s = to_stringable();
store.push_back(s);
store.push_back(std::cref(s));
fmt::vformat("", store);
}
TEST(args_test, named_int) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
store.push_back(fmt::arg("a1", 42));
EXPECT_EQ("42", fmt::vformat("{a1}", store));
}
TEST(args_test, named_strings) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
char str[] = "1234567890";
store.push_back(fmt::arg("a1", str));
store.push_back(fmt::arg("a2", std::cref(str)));
str[0] = 'X';
EXPECT_EQ("1234567890 and X234567890", fmt::vformat("{a1} and {a2}", store));
}
TEST(args_test, named_arg_by_ref) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
char band[] = "Rolling Stones";
store.push_back(fmt::arg("band", std::cref(band)));
band[9] = 'c'; // Changing band affects the output.
EXPECT_EQ(fmt::vformat("{band}", store), "Rolling Scones");
}
TEST(args_test, named_custom_format) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
auto c = custom_type();
store.push_back(fmt::arg("c1", c));
++c.i;
store.push_back(fmt::arg("c2", c));
++c.i;
store.push_back(fmt::arg("c_ref", std::cref(c)));
++c.i;
auto result = fmt::vformat("{c1} and {c2} and {c_ref}", store);
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
}
TEST(args_test, clear) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
store.push_back(42);
auto result = fmt::vformat("{}", store);
EXPECT_EQ("42", result);
store.push_back(43);
result = fmt::vformat("{} and {}", store);
EXPECT_EQ("42 and 43", result);
store.clear();
store.push_back(44);
result = fmt::vformat("{}", store);
EXPECT_EQ("44", result);
}
TEST(args_test, reserve) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
store.reserve(2, 1);
store.push_back(1.5f);
store.push_back(fmt::arg("a1", 42));
auto result = fmt::vformat("{a1} and {}", store);
EXPECT_EQ("42 and 1.5", result);
}
struct copy_throwable {
copy_throwable() {}
copy_throwable(const copy_throwable&) { throw "deal with it"; }
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<copy_throwable> {
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(copy_throwable, format_context& ctx) -> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(args_test, throw_on_copy) {
auto store = fmt::dynamic_format_arg_store<fmt::format_context>();
store.push_back(std::string("foo"));
try {
store.push_back(copy_throwable());
} catch (...) {
}
EXPECT_EQ(fmt::vformat("{}", store), "foo");
}

View File

@ -1,4 +1,8 @@
// Formatting library for C++ - assertion tests // Formatting library for C++ - FMT_ASSERT test
//
// It is a separate test to minimize the number of EXPECT_DEBUG_DEATH checks
// which are slow on some platforms. In other tests FMT_ASSERT is made to throw
// an exception which is much faster and easier to check.
// //
// Copyright (c) 2012 - present, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
@ -6,9 +10,9 @@
// For the license information refer to format.h. // For the license information refer to format.h.
#include "fmt/core.h" #include "fmt/core.h"
#include "gtest.h" #include "gtest/gtest.h"
TEST(AssertTest, Fail) { TEST(assert_test, fail) {
#if GTEST_HAS_DEATH_TEST #if GTEST_HAS_DEATH_TEST
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!"); EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
#else #else
@ -16,9 +20,8 @@ TEST(AssertTest, Fail) {
#endif #endif
} }
TEST(assert_test, dangling_else) {
bool test_condition = false; bool test_condition = false;
TEST(AssertTest, DanglingElse) {
bool executed_else = false; bool executed_else = false;
if (test_condition) if (test_condition)
FMT_ASSERT(true, ""); FMT_ASSERT(true, "");

View File

@ -5,77 +5,70 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#ifdef WIN32
# define _CRT_SECURE_NO_WARNINGS
#endif
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include <iomanip> #include "gtest-extra.h" // EXPECT_THROW_MSG
#include "util.h" // get_locale
#include "gtest-extra.h" using fmt::runtime;
std::tm make_tm() { using testing::Contains;
auto make_tm() -> std::tm {
auto time = std::tm(); auto time = std::tm();
time.tm_mday = 1; time.tm_mday = 1;
return time; return time;
} }
std::tm make_hour(int h) { auto make_hour(int h) -> std::tm {
auto time = make_tm(); auto time = make_tm();
time.tm_hour = h; time.tm_hour = h;
return time; return time;
} }
std::tm make_minute(int m) { auto make_minute(int m) -> std::tm {
auto time = make_tm(); auto time = make_tm();
time.tm_min = m; time.tm_min = m;
return time; return time;
} }
std::tm make_second(int s) { auto make_second(int s) -> std::tm {
auto time = make_tm(); auto time = make_tm();
time.tm_sec = s; time.tm_sec = s;
return time; return time;
} }
std::string format_tm(const std::tm& time, const char* spec, TEST(chrono_test, format_tm) {
const std::locale& loc) { auto tm = std::tm();
auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os;
os.imbue(loc);
facet.put(os, os, ' ', &time, spec, spec + std::strlen(spec));
return os.str();
}
TEST(TimeTest, Format) {
std::tm tm = std::tm();
tm.tm_year = 116; tm.tm_year = 116;
tm.tm_mon = 3; tm.tm_mon = 3;
tm.tm_mday = 25; tm.tm_mday = 25;
EXPECT_EQ("The date is 2016-04-25.", tm.tm_hour = 11;
fmt::format("The date is {:%Y-%m-%d}.", tm)); tm.tm_min = 22;
tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 2016-04-25 11:22:33.");
} }
TEST(TimeTest, GrowBuffer) { TEST(chrono_test, grow_buffer) {
std::string s = "{:"; auto s = std::string("{:");
for (int i = 0; i < 30; ++i) s += "%c"; for (int i = 0; i < 30; ++i) s += "%c";
s += "}\n"; s += "}\n";
std::time_t t = std::time(nullptr); auto t = std::time(nullptr);
fmt::format(s, *std::localtime(&t)); fmt::format(fmt::runtime(s), *std::localtime(&t));
} }
TEST(TimeTest, FormatToEmptyContainer) { TEST(chrono_test, format_to_empty_container) {
std::string s;
auto time = std::tm(); auto time = std::tm();
time.tm_sec = 42; time.tm_sec = 42;
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{:%S}", time); fmt::format_to(std::back_inserter(s), "{:%S}", time);
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");
} }
TEST(TimeTest, EmptyResult) { EXPECT_EQ("", fmt::format("{}", std::tm())); } TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
static bool EqualTime(const std::tm& lhs, const std::tm& rhs) { auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min && return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday && lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year && lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
@ -83,28 +76,39 @@ static bool EqualTime(const std::tm& lhs, const std::tm& rhs) {
lhs.tm_isdst == rhs.tm_isdst; lhs.tm_isdst == rhs.tm_isdst;
} }
TEST(TimeTest, LocalTime) { TEST(chrono_test, localtime) {
std::time_t t = std::time(nullptr); auto t = std::time(nullptr);
std::tm tm = *std::localtime(&t); auto tm = *std::localtime(&t);
EXPECT_TRUE(EqualTime(tm, fmt::localtime(t))); EXPECT_TRUE(equal(tm, fmt::localtime(t)));
} }
TEST(TimeTest, GMTime) { TEST(chrono_test, gmtime) {
std::time_t t = std::time(nullptr); auto t = std::time(nullptr);
std::tm tm = *std::gmtime(&t); auto tm = *std::gmtime(&t);
EXPECT_TRUE(EqualTime(tm, fmt::gmtime(t))); EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
} }
#define EXPECT_TIME(spec, time, duration) \ template <typename TimePoint> auto strftime(TimePoint tp) -> std::string {
{ \ auto t = std::chrono::system_clock::to_time_t(tp);
std::locale loc("ja_JP.utf8"); \ auto tm = *std::localtime(&t);
EXPECT_EQ(format_tm(time, spec, loc), \ char output[256] = {};
fmt::format(loc, "{:" spec "}", duration)); \ std::strftime(output, sizeof(output), "%Y-%m-%d %H:%M:%S", &tm);
return output;
}
TEST(chrono_test, time_point) {
auto t1 = std::chrono::system_clock::now();
EXPECT_EQ(strftime(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime(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(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
} }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
TEST(ChronoTest, FormatDefault) { TEST(chrono_test, format_default) {
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42))); EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds(42)));
EXPECT_EQ("42as", EXPECT_EQ("42as",
fmt::format("{}", std::chrono::duration<int, std::atto>(42))); fmt::format("{}", std::chrono::duration<int, std::atto>(42)));
@ -146,49 +150,7 @@ TEST(ChronoTest, FormatDefault) {
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42))); fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
} }
TEST(ChronoTest, FormatWide) { TEST(chrono_test, align) {
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(L"42as",
fmt::format(L"{}", std::chrono::duration<int, std::atto>(42)));
EXPECT_EQ(L"42fs",
fmt::format(L"{}", std::chrono::duration<int, std::femto>(42)));
EXPECT_EQ(L"42ps",
fmt::format(L"{}", std::chrono::duration<int, std::pico>(42)));
EXPECT_EQ(L"42ns", fmt::format(L"{}", std::chrono::nanoseconds(42)));
EXPECT_EQ(L"42\u00B5s", fmt::format(L"{}", std::chrono::microseconds(42)));
EXPECT_EQ(L"42ms", fmt::format(L"{}", std::chrono::milliseconds(42)));
EXPECT_EQ(L"42cs",
fmt::format(L"{}", std::chrono::duration<int, std::centi>(42)));
EXPECT_EQ(L"42ds",
fmt::format(L"{}", std::chrono::duration<int, std::deci>(42)));
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(L"42das",
fmt::format(L"{}", std::chrono::duration<int, std::deca>(42)));
EXPECT_EQ(L"42hs",
fmt::format(L"{}", std::chrono::duration<int, std::hecto>(42)));
EXPECT_EQ(L"42ks",
fmt::format(L"{}", std::chrono::duration<int, std::kilo>(42)));
EXPECT_EQ(L"42Ms",
fmt::format(L"{}", std::chrono::duration<int, std::mega>(42)));
EXPECT_EQ(L"42Gs",
fmt::format(L"{}", std::chrono::duration<int, std::giga>(42)));
EXPECT_EQ(L"42Ts",
fmt::format(L"{}", std::chrono::duration<int, std::tera>(42)));
EXPECT_EQ(L"42Ps",
fmt::format(L"{}", std::chrono::duration<int, std::peta>(42)));
EXPECT_EQ(L"42Es",
fmt::format(L"{}", std::chrono::duration<int, std::exa>(42)));
EXPECT_EQ(L"42m", fmt::format(L"{}", std::chrono::minutes(42)));
EXPECT_EQ(L"42h", fmt::format(L"{}", std::chrono::hours(42)));
EXPECT_EQ(
L"42[15]s",
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 1>>(42)));
EXPECT_EQ(
L"42[15/4]s",
fmt::format(L"{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
}
TEST(ChronoTest, Align) {
auto s = std::chrono::seconds(42); auto s = std::chrono::seconds(42);
EXPECT_EQ("42s ", fmt::format("{:5}", s)); EXPECT_EQ("42s ", fmt::format("{:5}", s));
EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5)); EXPECT_EQ("42s ", fmt::format("{:{}}", s, 5));
@ -204,7 +166,7 @@ TEST(ChronoTest, Align) {
fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12)); fmt::format("{:{}%H:%M:%S}", std::chrono::seconds(12345), 12));
} }
TEST(ChronoTest, FormatSpecs) { TEST(chrono_test, format_specs) {
EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0))); EXPECT_EQ("%", fmt::format("{:%%}", std::chrono::seconds(0)));
EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0))); EXPECT_EQ("\n", fmt::format("{:%n}", std::chrono::seconds(0)));
EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0))); EXPECT_EQ("\t", fmt::format("{:%t}", std::chrono::seconds(0)));
@ -233,42 +195,63 @@ TEST(ChronoTest, FormatSpecs) {
EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345))); EXPECT_EQ("s", fmt::format("{:%q}", std::chrono::seconds(12345)));
} }
TEST(ChronoTest, InvalidSpecs) { TEST(chrono_test, invalid_specs) {
auto sec = std::chrono::seconds(0); auto sec = std::chrono::seconds(0);
EXPECT_THROW_MSG(fmt::format("{:%a}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%a}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%A}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%c}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%A}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%x}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%Ex}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%c}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%X}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%EX}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%x}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%D}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%F}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%Ex}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%Ec}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%w}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%X}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%u}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%b}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%EX}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%B}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%z}", sec), fmt::format_error, "no date"); EXPECT_THROW_MSG(fmt::format(runtime("{:%D}"), sec), fmt::format_error,
EXPECT_THROW_MSG(fmt::format("{:%Z}", sec), fmt::format_error, "no date"); "no date");
EXPECT_THROW_MSG(fmt::format("{:%Eq}", sec), fmt::format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:%F}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Ec}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%w}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%u}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%b}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%B}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%z}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Z}"), sec), fmt::format_error,
"no date");
EXPECT_THROW_MSG(fmt::format(runtime("{:%Eq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
EXPECT_THROW_MSG(fmt::format("{:%Oq}", sec), fmt::format_error, EXPECT_THROW_MSG(fmt::format(runtime("{:%Oq}"), sec), fmt::format_error,
"invalid format"); "invalid format");
} }
TEST(ChronoTest, Locale) { auto format_tm(const std::tm& time, fmt::string_view spec,
const char* loc_name = "ja_JP.utf8"; const std::locale& loc) -> std::string {
bool has_locale = false; auto& facet = std::use_facet<std::time_put<char>>(loc);
std::locale loc; std::ostringstream os;
try { os.imbue(loc);
loc = std::locale(loc_name); facet.put(os, os, ' ', &time, spec.begin(), spec.end());
has_locale = true; return os.str();
} catch (const std::runtime_error&) {
} }
if (!has_locale) {
fmt::print("{} locale is missing.\n", loc_name); TEST(chrono_test, locale) {
return; auto loc = get_locale("ja_JP.utf8");
if (loc == std::locale::classic()) return;
# define EXPECT_TIME(spec, time, duration) \
{ \
auto jp_loc = std::locale("ja_JP.utf8"); \
EXPECT_EQ(format_tm(time, spec, jp_loc), \
fmt::format(jp_loc, "{:L" spec "}", duration)); \
} }
EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14)); EXPECT_TIME("%OH", make_hour(14), std::chrono::hours(14));
EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14)); EXPECT_TIME("%OI", make_hour(14), std::chrono::hours(14));
@ -283,9 +266,9 @@ TEST(ChronoTest, Locale) {
EXPECT_TIME("%p", time, sec); EXPECT_TIME("%p", time, sec);
} }
typedef std::chrono::duration<double, std::milli> dms; using dms = std::chrono::duration<double, std::milli>;
TEST(ChronoTest, FormatDefaultFP) { TEST(chrono_test, format_default_fp) {
typedef std::chrono::duration<float> fs; typedef std::chrono::duration<float> fs;
EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234))); EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234)));
typedef std::chrono::duration<float, std::milli> fms; typedef std::chrono::duration<float, std::milli> fms;
@ -295,15 +278,15 @@ TEST(ChronoTest, FormatDefaultFP) {
EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234))); EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234)));
} }
TEST(ChronoTest, FormatPrecision) { TEST(chrono_test, format_precision) {
EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)), EXPECT_THROW_MSG(fmt::format(runtime("{:.2}"), std::chrono::seconds(42)),
fmt::format_error, fmt::format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234)));
EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2));
} }
TEST(ChronoTest, FormatFullSpecs) { TEST(chrono_test, format_full_specs) {
EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234)));
EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2));
EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1));
@ -312,7 +295,7 @@ TEST(ChronoTest, FormatFullSpecs) {
EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234))); EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234)));
} }
TEST(ChronoTest, FormatSimpleQq) { TEST(chrono_test, format_simple_q) {
typedef std::chrono::duration<float> fs; typedef std::chrono::duration<float> fs;
EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234))); EXPECT_EQ("1.234 s", fmt::format("{:%Q %q}", fs(1.234)));
typedef std::chrono::duration<float, std::milli> fms; typedef std::chrono::duration<float, std::milli> fms;
@ -322,15 +305,15 @@ TEST(ChronoTest, FormatSimpleQq) {
EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234))); EXPECT_EQ("1.234 ms", fmt::format("{:%Q %q}", dms(1.234)));
} }
TEST(ChronoTest, FormatPrecisionQq) { TEST(chrono_test, format_precision_q) {
EXPECT_THROW_MSG(fmt::format("{:.2%Q %q}", std::chrono::seconds(42)), EXPECT_THROW_MSG(fmt::format(runtime("{:.2%Q %q}"), std::chrono::seconds(42)),
fmt::format_error, fmt::format_error,
"precision not allowed for this argument type"); "precision not allowed for this argument type");
EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms", fmt::format("{:.1%Q %q}", dms(1.234)));
EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2)); EXPECT_EQ("1.23 ms", fmt::format("{:.{}%Q %q}", dms(1.234), 2));
} }
TEST(ChronoTest, FormatFullSpecsQq) { TEST(chrono_test, format_full_specs_q) {
EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234))); EXPECT_EQ("1.2 ms ", fmt::format("{:7.1%Q %q}", dms(1.234)));
EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2)); EXPECT_EQ(" 1.23 ms", fmt::format("{:>8.{}%Q %q}", dms(1.234), 2));
EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1)); EXPECT_EQ(" 1.2 ms ", fmt::format("{:^{}.{}%Q %q}", dms(1.234), 8, 1));
@ -339,17 +322,17 @@ TEST(ChronoTest, FormatFullSpecsQq) {
EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234))); EXPECT_EQ("*1.2340 ms*", fmt::format("{:*^11.4%Q %q}", dms(1.234)));
} }
TEST(ChronoTest, InvalidWidthId) { TEST(chrono_test, invalid_width_id) {
EXPECT_THROW(fmt::format("{:{o}", std::chrono::seconds(0)), EXPECT_THROW(fmt::format(runtime("{:{o}"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }
TEST(ChronoTest, InvalidColons) { TEST(chrono_test, invalid_colons) {
EXPECT_THROW(fmt::format("{0}=:{0::", std::chrono::seconds(0)), EXPECT_THROW(fmt::format(runtime("{0}=:{0::"), std::chrono::seconds(0)),
fmt::format_error); fmt::format_error);
} }
TEST(ChronoTest, NegativeDurations) { TEST(chrono_test, negative_durations) {
EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345))); EXPECT_EQ("-12345", fmt::format("{:%Q}", std::chrono::seconds(-12345)));
EXPECT_EQ("-03:25:45", EXPECT_EQ("-03:25:45",
fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345))); fmt::format("{:%H:%M:%S}", std::chrono::seconds(-12345)));
@ -364,7 +347,7 @@ TEST(ChronoTest, NegativeDurations) {
fmt::format("{:%Q}", std::chrono::duration<int>(min))); fmt::format("{:%Q}", std::chrono::duration<int>(min)));
} }
TEST(ChronoTest, SpecialDurations) { TEST(chrono_test, special_durations) {
EXPECT_EQ( EXPECT_EQ(
"40.", "40.",
fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3)); fmt::format("{:%S}", std::chrono::duration<double>(1e20)).substr(0, 3));
@ -384,4 +367,19 @@ TEST(ChronoTest, SpecialDurations) {
"03:33:20"); "03:33:20");
} }
TEST(chrono_test, unsigned_duration) {
EXPECT_EQ("42s", fmt::format("{}", std::chrono::duration<unsigned>(42)));
}
TEST(chrono_test, weekday) {
auto loc = get_locale("ru_RU.UTF-8");
std::locale::global(loc);
auto mon = fmt::weekday(1);
EXPECT_EQ(fmt::format("{}", mon), "Mon");
if (loc != std::locale::classic()) {
EXPECT_THAT((std::vector<std::string>{"пн", "Пн", "пнд", "Пнд"}),
Contains(fmt::format(loc, "{:L}", mon)));
}
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR #endif // FMT_STATIC_THOUSANDS_SEPARATOR

View File

@ -7,52 +7,13 @@
#include "fmt/color.h" #include "fmt/color.h"
#include "gtest-extra.h" #include <iterator> // std::back_inserter
TEST(ColorsTest, ColorsPrint) { #include "gtest-extra.h" // EXPECT_WRITE
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_WRITE(
stdout,
fmt::print(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::bold, "bold"),
"\x1b[1mbold\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::italic, "italic"),
"\x1b[3mitalic\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::underline, "underline"),
"\x1b[4munderline\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(fmt::emphasis::strikethrough, "strikethrough"),
"\x1b[9mstrikethrough\x1b[0m");
EXPECT_WRITE(
stdout,
fmt::print(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
EXPECT_WRITE(stderr, fmt::print(stderr, fmt::emphasis::bold, "bold error"),
"\x1b[1mbold error\x1b[0m");
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
"\x1b[38;2;000;000;255mblue log\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
"\x1b[31mtred\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
"\x1b[46mtcyan\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
"\x1b[92mtbgreen\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
"\x1b[105mtbmagenta\x1b[0m");
}
TEST(ColorsTest, Format) { TEST(color_test, format) {
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"), EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m"); "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"), EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m"); "\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_EQ( EXPECT_EQ(
@ -84,3 +45,16 @@ TEST(ColorsTest, Format) {
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"), EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
"\x1b[31mfoo\x1b[0m"); "\x1b[31mfoo\x1b[0m");
} }
TEST(color_test, format_to) {
auto out = std::string();
fmt::format_to(std::back_inserter(out), fg(fmt::rgb(255, 20, 30)),
"rgb(255,20,30){}{}{}", 1, 2, 3);
EXPECT_EQ(fmt::to_string(out),
"\x1b[38;2;255;020;030mrgb(255,20,30)123\x1b[0m");
}
TEST(color_test, print) {
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
}

View File

@ -1,6 +1,6 @@
# Test if compile errors are produced where necessary. # Test if compile errors are produced where necessary.
cmake_minimum_required(VERSION 3.1.0) cmake_minimum_required(VERSION 3.1...3.18)
include(CheckCXXSourceCompiles) include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag) include(CheckCXXCompilerFlag)

View File

@ -5,138 +5,83 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#include <stdint.h>
#include <cctype>
#include <cfloat>
#include <climits>
#include <cmath>
#include <cstring>
#include <deque>
#include <list>
#include <memory>
#include <string>
// Check if fmt/compile.h compiles with windows.h included before it.
#ifdef _WIN32
# include <windows.h>
#endif
#include "fmt/compile.h" #include "fmt/compile.h"
#include "gmock.h"
#include <type_traits>
#include "fmt/chrono.h"
#include "gmock/gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include "mock-allocator.h"
#include "util.h"
#undef ERROR TEST(iterator_test, counting_iterator) {
#undef min auto it = fmt::detail::counting_iterator();
#undef max auto prev = it++;
EXPECT_EQ(prev.count(), 0);
using testing::Return; EXPECT_EQ(it.count(), 1);
using testing::StrictMock; EXPECT_EQ((it + 41).count(), 42);
// compiletime_prepared_parts_type_provider is useful only with relaxed
// constexpr.
#if FMT_USE_CONSTEXPR
template <unsigned EXPECTED_PARTS_COUNT, typename Format>
void check_prepared_parts_type(Format format) {
typedef fmt::detail::compiled_format_base<decltype(format)> provider;
typedef fmt::detail::format_part<char>
expected_parts_type[EXPECTED_PARTS_COUNT];
static_assert(std::is_same<typename provider::parts_container,
expected_parts_type>::value,
"CompileTimePreparedPartsTypeProvider test failed");
} }
TEST(CompileTest, CompileTimePreparedPartsTypeProvider) { TEST(iterator_test, truncating_iterator) {
check_prepared_parts_type<1u>(FMT_STRING("text")); char* p = nullptr;
check_prepared_parts_type<1u>(FMT_STRING("{}")); auto it = fmt::detail::truncating_iterator<char*>(p, 3);
check_prepared_parts_type<2u>(FMT_STRING("text{}")); auto prev = it++;
check_prepared_parts_type<2u>(FMT_STRING("{}text")); EXPECT_EQ(prev.base(), p);
check_prepared_parts_type<3u>(FMT_STRING("text{}text")); EXPECT_EQ(it.base(), p + 1);
check_prepared_parts_type<3u>(FMT_STRING("{:{}.{}} {:{}}")); }
check_prepared_parts_type<3u>(FMT_STRING("{{{}}}")); // '{', 'argument', '}' TEST(iterator_test, truncating_iterator_default_construct) {
check_prepared_parts_type<2u>(FMT_STRING("text{{")); // 'text', '{' auto it = fmt::detail::truncating_iterator<char*>();
check_prepared_parts_type<3u>(FMT_STRING("text{{ ")); // 'text', '{', ' ' EXPECT_EQ(nullptr, it.base());
check_prepared_parts_type<2u>(FMT_STRING("}}text")); // '}', text EXPECT_EQ(std::size_t{0}, it.count());
check_prepared_parts_type<2u>(FMT_STRING("text}}text")); // 'text}', 'text' }
check_prepared_parts_type<4u>(
FMT_STRING("text{{}}text")); // 'text', '{', '}', 'text' #ifdef __cpp_lib_ranges
TEST(iterator_test, truncating_iterator_is_output_iterator) {
static_assert(
std::output_iterator<fmt::detail::truncating_iterator<char*>, char>);
} }
#endif #endif
TEST(CompileTest, PassStringLiteralFormat) { TEST(iterator_test, truncating_back_inserter) {
const auto prepared = fmt::detail::compile<int>("test {}"); auto buffer = std::string();
EXPECT_EQ("test 42", fmt::format(prepared, 42)); auto bi = std::back_inserter(buffer);
const auto wprepared = fmt::detail::compile<int>(L"test {}"); auto it = fmt::detail::truncating_iterator<decltype(bi)>(bi, 2);
EXPECT_EQ(L"test 42", fmt::format(wprepared, 42)); *it++ = '4';
*it++ = '2';
*it++ = '1';
EXPECT_EQ(buffer.size(), 2);
EXPECT_EQ(buffer, "42");
} }
TEST(CompileTest, FormatToArrayOfChars) { TEST(compile_test, compile_fallback) {
char buffer[32] = {0}; // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is
const auto prepared = fmt::detail::compile<int>("4{}"); // not available.
fmt::format_to(fmt::detail::make_checked(buffer, 32), prepared, 2); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
EXPECT_EQ(std::string("42"), buffer);
wchar_t wbuffer[32] = {0};
const auto wprepared = fmt::detail::compile<int>(L"4{}");
fmt::format_to(fmt::detail::make_checked(wbuffer, 32), wprepared, 2);
EXPECT_EQ(std::wstring(L"42"), wbuffer);
} }
TEST(CompileTest, FormatToIterator) { #ifdef __cpp_if_constexpr
std::string s(2, ' '); struct test_formattable {};
const auto prepared = fmt::detail::compile<int>("4{}");
fmt::format_to(s.begin(), prepared, 2);
EXPECT_EQ("42", s);
std::wstring ws(2, L' ');
const auto wprepared = fmt::detail::compile<int>(L"4{}");
fmt::format_to(ws.begin(), wprepared, 2);
EXPECT_EQ(L"42", ws);
}
TEST(CompileTest, FormatToN) {
char buf[5];
auto f = fmt::detail::compile<int>("{:10}");
auto result = fmt::format_to_n(buf, 5, f, 42);
EXPECT_EQ(result.size, 10);
EXPECT_EQ(result.out, buf + 5);
EXPECT_EQ(fmt::string_view(buf, 5), " ");
}
TEST(CompileTest, FormattedSize) {
auto f = fmt::detail::compile<int>("{:10}");
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
}
TEST(CompileTest, MultipleTypes) {
auto f = fmt::detail::compile<int, int>("{} {}");
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
}
struct formattable {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> struct formatter<formattable> : formatter<const char*> { template <> struct formatter<test_formattable> : formatter<const char*> {
char word_spec = 'f';
constexpr auto parse(format_parse_context& ctx) {
auto it = ctx.begin(), end = ctx.end();
if (it == end || *it == '}') return it;
if (it != end && (*it == 'f' || *it == 'b')) word_spec = *it++;
if (it != end && *it != '}') throw format_error("invalid format");
return it;
}
template <typename FormatContext> template <typename FormatContext>
auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) { constexpr auto format(test_formattable, FormatContext& ctx) const
return formatter<const char*>::format("foo", ctx); -> decltype(ctx.out()) {
return formatter<const char*>::format(word_spec == 'f' ? "foo" : "bar",
ctx);
} }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
TEST(CompileTest, FormatUserDefinedType) { TEST(compile_test, format_default) {
auto f = fmt::detail::compile<formattable>("{}");
EXPECT_EQ(fmt::format(f, formattable()), "foo");
}
TEST(CompileTest, EmptyFormatString) {
auto f = fmt::detail::compile<>("");
EXPECT_EQ(fmt::format(f), "");
}
#ifdef __cpp_if_constexpr
TEST(CompileTest, FormatDefault) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42));
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u));
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll));
@ -146,21 +91,269 @@ TEST(CompileTest, FormatDefault) {
EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2)); EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo")); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo"));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo"))); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo")));
EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), formattable())); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), test_formattable()));
auto t = std::chrono::system_clock::now();
EXPECT_EQ(fmt::format("{}", t), fmt::format(FMT_COMPILE("{}"), t));
# ifdef __cpp_lib_byte
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), std::byte{42}));
# endif
} }
TEST(CompileTest, FormatSpecs) { TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
EXPECT_EQ("1.2 ms ",
fmt::format(FMT_COMPILE("{:7.1%Q %q}"),
std::chrono::duration<double, std::milli>(1.234)));
} }
TEST(CompileTest, FormatTo) { TEST(compile_test, dynamic_format_specs) {
EXPECT_EQ("foo ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5));
EXPECT_EQ(" 3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2));
EXPECT_EQ(
"=1.234ms=",
fmt::format(FMT_COMPILE("{:=^{}.{}}"),
std::chrono::duration<double, std::milli>(1.234), 9, 3));
}
TEST(compile_test, manual_ordering) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42));
EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{1} {0}"), 43, 41));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {2}"), 41, 42, 43));
EXPECT_EQ(" 41 43", fmt::format(FMT_COMPILE("{1:{2}} {0:4}"), 43, 41, 4));
EXPECT_EQ("42 1.2 ms ",
fmt::format(FMT_COMPILE("{0} {1:7.1%Q %q}"), 42,
std::chrono::duration<double, std::milli>(1.234)));
EXPECT_EQ(
"true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
}
TEST(compile_test, named) {
auto runtime_named_field_compiled =
fmt::detail::compile<decltype(fmt::arg("arg", 42))>(FMT_COMPILE("{arg}"));
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
EXPECT_EQ("foofoo", fmt::format(FMT_COMPILE("{a0}{}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
EXPECT_EQ("foobar", fmt::format(FMT_COMPILE("{a0}{1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{}{a1}"), "foo", fmt::arg("a1", "bar")));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a1", "bar"),
fmt::arg("a2", "baz"), fmt::arg("a0", "foo")));
EXPECT_EQ(" bar foo ",
fmt::format(FMT_COMPILE(" {foo} {bar} "), fmt::arg("foo", "bar"),
fmt::arg("bar", "foo")));
EXPECT_THROW(fmt::format(FMT_COMPILE("{invalid}"), fmt::arg("valid", 42)),
fmt::format_error);
# if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
using namespace fmt::literals;
auto statically_named_field_compiled =
fmt::detail::compile<decltype("arg"_a = 42)>(FMT_COMPILE("{arg}"));
static_assert(std::is_same_v<decltype(statically_named_field_compiled),
fmt::detail::field<char, int, 0>>);
EXPECT_EQ("41 43",
fmt::format(FMT_COMPILE("{a0} {a1}"), "a0"_a = 41, "a1"_a = 43));
EXPECT_EQ("41 43",
fmt::format(FMT_COMPILE("{a1} {a0}"), "a0"_a = 43, "a1"_a = 41));
# endif
}
TEST(compile_test, format_to) {
char buf[8]; char buf[8];
auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42); auto end = fmt::format_to(buf, FMT_COMPILE("{}"), 42);
*end = '\0'; *end = '\0';
EXPECT_STREQ("42", buf); EXPECT_STREQ("42", buf);
end = fmt::format_to(buf, FMT_COMPILE("{:x}"), 42);
*end = '\0';
EXPECT_STREQ("2a", buf);
} }
TEST(CompileTest, TextAndArg) { TEST(compile_test, format_to_n) {
constexpr auto buffer_size = 8;
char buffer[buffer_size];
auto res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{}"), 42);
*res.out = '\0';
EXPECT_STREQ("42", buffer);
res = fmt::format_to_n(buffer, buffer_size, FMT_COMPILE("{:x}"), 42);
*res.out = '\0';
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));
}
TEST(compile_test, text_and_arg) {
EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); EXPECT_EQ(">>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42));
EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42));
}
TEST(compile_test, unknown_format_fallback) {
EXPECT_EQ(" 42 ",
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
std::vector<char> v;
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"),
fmt::arg("name", 42));
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size()));
char buffer[4];
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
fmt::arg("name", 42));
EXPECT_EQ(5u, result.size);
EXPECT_EQ(buffer + 4, result.out);
EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4));
}
TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); }
struct to_stringable {
friend fmt::string_view to_string_view(to_stringable) { return {}; }
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<to_stringable> {
auto parse(format_parse_context& ctx) const -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const to_stringable&, FormatContext& ctx) -> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(compile_test, to_string_and_formatter) {
fmt::format(FMT_COMPILE("{}"), to_stringable());
}
TEST(compile_test, print) {
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!");
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!");
}
#endif
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
}
#endif
#if __cplusplus >= 202002L || \
(__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, bool) {
EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true));
EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false));
EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true));
EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true));
}
TEST(compile_time_formatting_test, integer) {
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42));
EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420));
EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42));
EXPECT_EQ("42 42",
test_format<6>(FMT_COMPILE("{} {}"), uint32_t{42}, uint64_t{42}));
EXPECT_EQ("+42", test_format<4>(FMT_COMPILE("{:+}"), 42));
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{:-}"), 42));
EXPECT_EQ(" 42", test_format<4>(FMT_COMPILE("{: }"), 42));
EXPECT_EQ("-0042", test_format<6>(FMT_COMPILE("{:05}"), -42));
EXPECT_EQ("101010", test_format<7>(FMT_COMPILE("{:b}"), 42));
EXPECT_EQ("0b101010", test_format<9>(FMT_COMPILE("{:#b}"), 42));
EXPECT_EQ("0B101010", test_format<9>(FMT_COMPILE("{:#B}"), 42));
EXPECT_EQ("042", test_format<4>(FMT_COMPILE("{:#o}"), 042));
EXPECT_EQ("0x4a", test_format<5>(FMT_COMPILE("{:#x}"), 0x4a));
EXPECT_EQ("0X4A", test_format<5>(FMT_COMPILE("{:#X}"), 0x4a));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ll));
EXPECT_EQ(" 42", test_format<6>(FMT_COMPILE("{:5}"), 42ull));
EXPECT_EQ("42 ", test_format<5>(FMT_COMPILE("{:<4}"), 42));
EXPECT_EQ(" 42", test_format<5>(FMT_COMPILE("{:>4}"), 42));
EXPECT_EQ(" 42 ", test_format<5>(FMT_COMPILE("{:^4}"), 42));
EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42));
}
TEST(compile_time_formatting_test, char) {
EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c'));
EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c'));
EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c'));
}
TEST(compile_time_formatting_test, string) {
EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42"));
EXPECT_EQ("The answer is 42",
test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42"));
EXPECT_EQ("abc**", test_format<6>(FMT_COMPILE("{:*<5}"), "abc"));
EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡"));
}
TEST(compile_time_formatting_test, combination) {
EXPECT_EQ("420, true, answer",
test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer"));
EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4));
}
TEST(compile_time_formatting_test, custom_type) {
EXPECT_EQ("foo", test_format<4>(FMT_COMPILE("{}"), test_formattable()));
EXPECT_EQ("bar", test_format<4>(FMT_COMPILE("{:b}"), test_formattable()));
}
TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include <iterator>
#include <vector>
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/format.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "fmt/xchar.h"
// Exercise the API to verify that everything we expect to can compile.
void test_format_api() {
fmt::format(FMT_STRING("{}"), 42);
fmt::format(FMT_STRING(L"{}"), 42);
fmt::format(FMT_STRING("noop"));
fmt::to_string(42);
fmt::to_wstring(42);
std::vector<char> out;
fmt::format_to(std::back_inserter(out), FMT_STRING("{}"), 42);
char buffer[4];
fmt::format_to_n(buffer, 3, FMT_STRING("{}"), 12345);
wchar_t wbuffer[4];
fmt::format_to_n(wbuffer, 3, FMT_STRING(L"{}"), 12345);
}
void test_chrono() {
fmt::format(FMT_STRING("{}"), std::chrono::seconds(42));
fmt::format(FMT_STRING(L"{}"), std::chrono::seconds(42));
}
void test_text_style() {
fmt::print(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
fmt::format(fg(fmt::rgb(255, 20, 30)), FMT_STRING("{}"), "rgb(255,20,30)");
fmt::text_style ts = fg(fmt::rgb(255, 20, 30));
std::string out;
fmt::format_to(std::back_inserter(out), ts,
FMT_STRING("rgb(255,20,30){}{}{}"), 1, 2, 3);
}
void test_range() {
std::vector<char> hello = {'h', 'e', 'l', 'l', 'o'};
fmt::format(FMT_STRING("{}"), hello);
}
int main() {
test_format_api();
test_chrono();
test_text_style();
test_range();
}

View File

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

View File

@ -8,6 +8,7 @@
#ifndef FMT_FORMAT_ #ifndef FMT_FORMAT_
#define FMT_FORMAT_ #define FMT_FORMAT_
#include <algorithm>
#include <cassert> #include <cassert>
#include <variant> #include <variant>
#include "fmt/format.h" #include "fmt/format.h"
@ -38,9 +39,9 @@ namespace std {
template<class Out, class charT> class basic_format_context; template<class Out, class charT> class basic_format_context;
using format_context = basic_format_context< using format_context = basic_format_context<
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<char>>, char>; /* unspecified */ fmt::detail::buffer_appender<char>, char>;
using wformat_context = basic_format_context< using wformat_context = basic_format_context<
/* unspecified */ std::back_insert_iterator<fmt::detail::buffer<wchar_t>>, wchar_t>; /* unspecified */ fmt::detail::buffer_appender<wchar_t>, wchar_t>;
template<class T, class charT = char> struct formatter { template<class T, class charT = char> struct formatter {
formatter() = delete; formatter() = delete;
@ -714,7 +715,7 @@ string vformat(string_view fmt, format_args args) {
fmt::detail::buffer<char>& buf = mbuf; fmt::detail::buffer<char>& buf = mbuf;
using af = detail::arg_formatter<fmt::format_context::iterator, char>; using af = detail::arg_formatter<fmt::format_context::iterator, char>;
detail::format_handler<af, char, format_context> detail::format_handler<af, char, format_context>
h(std::back_inserter(buf), fmt, args, {}); h(fmt::detail::buffer_appender<char>(buf), fmt, args, {});
fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h); fmt::detail::parse_format_string<false>(fmt::to_string_view(fmt), h);
return to_string(mbuf); return to_string(mbuf);
} }

View File

@ -5,21 +5,16 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#define FMT_NOEXCEPT
#undef FMT_SHARED
#include "test-assert.h"
// Include format.cc instead of format.h to test implementation.
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include "../src/format.cc" // clang-format off
#include "fmt/printf.h" #include "test-assert.h"
#include "gmock.h" // clang-format on
#include "gtest-extra.h"
#include "util.h"
#undef max #include "fmt/format.h"
#include "gmock/gmock.h"
#include "util.h"
using fmt::detail::bigint; using fmt::detail::bigint;
using fmt::detail::fp; using fmt::detail::fp;
@ -28,13 +23,13 @@ using fmt::detail::max_value;
static_assert(!std::is_copy_constructible<bigint>::value, ""); static_assert(!std::is_copy_constructible<bigint>::value, "");
static_assert(!std::is_copy_assignable<bigint>::value, ""); static_assert(!std::is_copy_assignable<bigint>::value, "");
TEST(BigIntTest, Construct) { TEST(bigint_test, construct) {
EXPECT_EQ("", fmt::format("{}", bigint())); EXPECT_EQ("", fmt::format("{}", bigint()));
EXPECT_EQ("42", fmt::format("{}", bigint(0x42))); EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0))); EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
} }
TEST(BigIntTest, Compare) { TEST(bigint_test, compare) {
bigint n1(42); bigint n1(42);
bigint n2(42); bigint n2(42);
EXPECT_EQ(compare(n1, n2), 0); EXPECT_EQ(compare(n1, n2), 0);
@ -48,7 +43,7 @@ TEST(BigIntTest, Compare) {
EXPECT_GT(compare(n4, n2), 0); EXPECT_GT(compare(n4, n2), 0);
} }
TEST(BigIntTest, AddCompare) { TEST(bigint_test, add_compare) {
EXPECT_LT( EXPECT_LT(
add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0); add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0); EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
@ -74,7 +69,7 @@ TEST(BigIntTest, AddCompare) {
0); 0);
} }
TEST(BigIntTest, ShiftLeft) { TEST(bigint_test, shift_left) {
bigint n(0x42); bigint n(0x42);
n <<= 0; n <<= 0;
EXPECT_EQ("42", fmt::format("{}", n)); EXPECT_EQ("42", fmt::format("{}", n));
@ -84,7 +79,7 @@ TEST(BigIntTest, ShiftLeft) {
EXPECT_EQ("108000000", fmt::format("{}", n)); EXPECT_EQ("108000000", fmt::format("{}", n));
} }
TEST(BigIntTest, Multiply) { TEST(bigint_test, multiply) {
bigint n(0x42); bigint n(0x42);
EXPECT_THROW(n *= 0, assertion_failure); EXPECT_THROW(n *= 0, assertion_failure);
n *= 1; n *= 1;
@ -101,7 +96,7 @@ TEST(BigIntTest, Multiply) {
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax)); EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
} }
TEST(BigIntTest, Accumulator) { TEST(bigint_test, accumulator) {
fmt::detail::accumulator acc; fmt::detail::accumulator acc;
EXPECT_EQ(acc.lower, 0); EXPECT_EQ(acc.lower, 0);
EXPECT_EQ(acc.upper, 0); EXPECT_EQ(acc.upper, 0);
@ -110,7 +105,7 @@ TEST(BigIntTest, Accumulator) {
EXPECT_EQ(static_cast<uint32_t>(acc), 34); EXPECT_EQ(static_cast<uint32_t>(acc), 34);
acc += 56; acc += 56;
EXPECT_EQ(acc.lower, 90); EXPECT_EQ(acc.lower, 90);
acc += fmt::detail::max_value<uint64_t>(); acc += max_value<uint64_t>();
EXPECT_EQ(acc.upper, 13); EXPECT_EQ(acc.upper, 13);
EXPECT_EQ(acc.lower, 89); EXPECT_EQ(acc.lower, 89);
acc >>= 32; acc >>= 32;
@ -118,7 +113,7 @@ TEST(BigIntTest, Accumulator) {
EXPECT_EQ(acc.lower, 13 * 0x100000000); EXPECT_EQ(acc.lower, 13 * 0x100000000);
} }
TEST(BigIntTest, Square) { TEST(bigint_test, square) {
bigint n0(0); bigint n0(0);
n0.square(); n0.square();
EXPECT_EQ("0", fmt::format("{}", n0)); EXPECT_EQ("0", fmt::format("{}", n0));
@ -136,18 +131,18 @@ TEST(BigIntTest, Square) {
EXPECT_EQ("2540be400", fmt::format("{}", n4)); EXPECT_EQ("2540be400", fmt::format("{}", n4));
} }
TEST(BigIntTest, DivModAssignZeroDivisor) { TEST(bigint_test, divmod_assign_zero_divisor) {
bigint zero(0); bigint zero(0);
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure); EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure); EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
} }
TEST(BigIntTest, DivModAssignSelf) { TEST(bigint_test, divmod_assign_self) {
bigint n(100); bigint n(100);
EXPECT_THROW(n.divmod_assign(n), assertion_failure); EXPECT_THROW(n.divmod_assign(n), assertion_failure);
} }
TEST(BigIntTest, DivModAssignUnaligned) { TEST(bigint_test, divmod_assign_unaligned) {
// (42 << 340) / pow(10, 100): // (42 << 340) / pow(10, 100):
bigint n1(42); bigint n1(42);
n1 <<= 340; n1 <<= 340;
@ -159,7 +154,7 @@ TEST(BigIntTest, DivModAssignUnaligned) {
fmt::format("{}", n1)); fmt::format("{}", n1));
} }
TEST(BigIntTest, DivModAssign) { TEST(bigint_test, divmod_assign) {
// 100 / 10: // 100 / 10:
bigint n1(100); bigint n1(100);
int result = n1.divmod_assign(bigint(10)); int result = n1.divmod_assign(bigint(10));
@ -186,70 +181,20 @@ template <bool is_iec559> void run_double_tests() {
template <> void run_double_tests<true>() { template <> void run_double_tests<true>() {
// Construct from double. // Construct from double.
EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52)); EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
// Compute boundaries:
fp value;
// Normalized & not power of 2 - equidistant boundaries:
auto b = value.assign_with_boundaries(1.23);
EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52));
EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00);
EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400);
// Normalized power of 2 - lower boundary is closer:
b = value.assign_with_boundaries(1.9807040628566084e+28); // 2**94
EXPECT_EQ(value, fp(0x0010000000000000, 42));
EXPECT_EQ(b.lower, 0x7ffffffffffffe00);
EXPECT_EQ(b.upper, 0x8000000000000400);
// Smallest normalized double - equidistant boundaries:
b = value.assign_with_boundaries(2.2250738585072014e-308);
EXPECT_EQ(value, fp(0x0010000000000000, -1074));
EXPECT_EQ(b.lower, 0x7ffffffffffffc00);
EXPECT_EQ(b.upper, 0x8000000000000400);
// Subnormal - equidistant boundaries:
b = value.assign_with_boundaries(4.9406564584124654e-324);
EXPECT_EQ(value, fp(0x0000000000000001, -1074));
EXPECT_EQ(b.lower, 0x4000000000000000);
EXPECT_EQ(b.upper, 0xc000000000000000);
} }
TEST(FPTest, DoubleTests) { TEST(fp_test, double_tests) {
run_double_tests<std::numeric_limits<double>::is_iec559>(); run_double_tests<std::numeric_limits<double>::is_iec559>();
} }
TEST(FPTest, Normalize) { TEST(fp_test, normalize) {
const auto v = fp(0xbeef, 42); const auto v = fp(0xbeef, 42);
auto normalized = normalize(v); auto normalized = normalize(v);
EXPECT_EQ(0xbeef000000000000, normalized.f); EXPECT_EQ(0xbeef000000000000, normalized.f);
EXPECT_EQ(-6, normalized.e); EXPECT_EQ(-6, normalized.e);
} }
TEST(FPTest, ComputeFloatBoundaries) { TEST(fp_test, multiply) {
struct {
double x, lower, upper;
} tests[] = {
// regular
{1.5f, 1.4999999403953552, 1.5000000596046448},
// boundary
{1.0f, 0.9999999701976776, 1.0000000596046448},
// min normal
{1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38},
// max subnormal
{1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38},
// min subnormal
{1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45},
};
for (auto test : tests) {
fp vlower = normalize(fp(test.lower));
fp vupper = normalize(fp(test.upper));
vlower.f >>= vupper.e - vlower.e;
vlower.e = vupper.e;
fp value;
auto b = value.assign_float_with_boundaries(test.x);
EXPECT_EQ(vlower.f, b.lower);
EXPECT_EQ(vupper.f, b.upper);
}
}
TEST(FPTest, Multiply) {
auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7); auto v = fp(123ULL << 32, 4) * fp(56ULL << 32, 7);
EXPECT_EQ(v.f, 123u * 56u); EXPECT_EQ(v.f, 123u * 56u);
EXPECT_EQ(v.e, 4 + 7 + 64); EXPECT_EQ(v.e, 4 + 7 + 64);
@ -258,19 +203,57 @@ TEST(FPTest, Multiply) {
EXPECT_EQ(v.e, 4 + 8 + 64); EXPECT_EQ(v.e, 4 + 8 + 64);
} }
TEST(FPTest, GetCachedPower) { TEST(fp_test, get_cached_power) {
typedef std::numeric_limits<double> limits; using limits = std::numeric_limits<double>;
for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) { for (auto exp = limits::min_exponent; exp <= limits::max_exponent; ++exp) {
int dec_exp = 0; int dec_exp = 0;
auto fp = fmt::detail::get_cached_power(exp, dec_exp); auto fp = fmt::detail::get_cached_power(exp, dec_exp);
EXPECT_LE(exp, fp.e); bigint exact, cache(fp.f);
int dec_exp_step = 8; if (dec_exp >= 0) {
EXPECT_LE(fp.e, exp + dec_exp_step * log2(10)); exact.assign_pow10(dec_exp);
EXPECT_DOUBLE_EQ(pow(10, dec_exp), ldexp(static_cast<double>(fp.f), fp.e)); if (fp.e <= 0)
exact <<= -fp.e;
else
cache <<= fp.e;
exact.align(cache);
cache.align(exact);
auto exact_str = fmt::format("{}", exact);
auto cache_str = fmt::format("{}", 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 *= fp.f + 1; // Inexact check.
exact.assign(1);
exact <<= -fp.e;
exact.align(cache);
auto exact_str = fmt::format("{}", exact);
auto cache_str = fmt::format("{}", cache);
EXPECT_EQ(exact_str.size(), cache_str.size());
EXPECT_EQ(exact_str.substr(0, 16), cache_str.substr(0, 16));
}
} }
} }
TEST(FPTest, GetRoundDirection) { TEST(fp_test, dragonbox_max_k) {
using fmt::detail::dragonbox::floor_log10_pow2;
using float_info = fmt::detail::dragonbox::float_info<float>;
EXPECT_EQ(fmt::detail::const_check(float_info::max_k),
float_info::kappa - floor_log10_pow2(float_info::min_exponent -
float_info::significand_bits));
using double_info = fmt::detail::dragonbox::float_info<double>;
EXPECT_EQ(
fmt::detail::const_check(double_info::max_k),
double_info::kappa - floor_log10_pow2(double_info::min_exponent -
double_info::significand_bits));
}
TEST(fp_test, get_round_direction) {
using fmt::detail::get_round_direction; using fmt::detail::get_round_direction;
using fmt::detail::round_direction; using fmt::detail::round_direction;
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0)); EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
@ -294,7 +277,7 @@ TEST(FPTest, GetRoundDirection) {
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1)); EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
} }
TEST(FPTest, FixedHandler) { TEST(fp_test, fixed_handler) {
struct handler : fmt::detail::fixed_handler { struct handler : fmt::detail::fixed_handler {
char buffer[10]; char buffer[10];
handler(int prec = 0) : fmt::detail::fixed_handler() { handler(int prec = 0) : fmt::detail::fixed_handler() {
@ -307,7 +290,7 @@ TEST(FPTest, FixedHandler) {
EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false), EXPECT_THROW(handler().on_digit('0', 100, 100, 0, exp, false),
assertion_failure); assertion_failure);
namespace digits = fmt::detail::digits; namespace digits = fmt::detail::digits;
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::done); EXPECT_EQ(handler(1).on_digit('0', 100, 10, 10, exp, false), digits::error);
// Check that divisor - error doesn't overflow. // Check that divisor - error doesn't overflow.
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error); EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
// Check that 2 * error doesn't overflow. // Check that 2 * error doesn't overflow.
@ -316,94 +299,23 @@ TEST(FPTest, FixedHandler) {
digits::error); digits::error);
} }
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) { TEST(fp_test, grisu_format_compiles_with_on_ieee_double) {
fmt::memory_buffer buf; fmt::memory_buffer buf;
format_float(0.42, -1, fmt::detail::float_specs(), buf); format_float(0.42, -1, fmt::detail::float_specs(), buf);
} }
template <typename T> struct value_extractor { TEST(format_impl_test, format_error_code) {
T operator()(T value) { return value; }
template <typename U> FMT_NORETURN T operator()(U) {
throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
}
#if FMT_USE_INT128
// Apple Clang does not define typeid for __int128_t and __uint128_t.
FMT_NORETURN T operator()(fmt::detail::int128_t) {
throw std::runtime_error("invalid type __int128_t");
}
FMT_NORETURN T operator()(fmt::detail::uint128_t) {
throw std::runtime_error("invalid type __uint128_t");
}
#endif
};
TEST(FormatTest, ArgConverter) {
long long value = max_value<long long>();
auto arg = fmt::detail::make_arg<fmt::format_context>(value);
fmt::visit_format_arg(
fmt::detail::arg_converter<long long, fmt::format_context>(arg, 'd'),
arg);
EXPECT_EQ(value, fmt::visit_format_arg(value_extractor<long long>(), arg));
}
TEST(FormatTest, FormatNegativeNaN) {
double nan = std::numeric_limits<double>::quiet_NaN();
if (std::signbit(-nan))
EXPECT_EQ("-nan", fmt::format("{}", -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
}
TEST(FormatTest, StrError) {
char* message = nullptr;
char buffer[BUFFER_SIZE];
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = nullptr, 0),
"invalid buffer");
EXPECT_ASSERT(fmt::detail::safe_strerror(EDOM, message = buffer, 0),
"invalid buffer");
buffer[0] = 'x';
#if defined(_GNU_SOURCE) && !defined(__COVERITY__)
// Use invalid error code to make sure that safe_strerror returns an error
// message in the buffer rather than a pointer to a static string.
int error_code = -1;
#else
int error_code = EDOM;
#endif
int result =
fmt::detail::safe_strerror(error_code, message = buffer, BUFFER_SIZE);
EXPECT_EQ(result, 0);
size_t message_size = std::strlen(message);
EXPECT_GE(BUFFER_SIZE - 1u, message_size);
EXPECT_EQ(get_system_error(error_code), message);
// safe_strerror never uses buffer on MinGW.
#if !defined(__MINGW32__) && !defined(__sun)
result =
fmt::detail::safe_strerror(error_code, message = buffer, message_size);
EXPECT_EQ(ERANGE, result);
result = fmt::detail::safe_strerror(error_code, message = buffer, 1);
EXPECT_EQ(buffer, message); // Message should point to buffer.
EXPECT_EQ(ERANGE, result);
EXPECT_STREQ("", message);
#endif
}
TEST(FormatTest, FormatErrorCode) {
std::string msg = "error 42", sep = ": "; std::string msg = "error 42", sep = ": ";
{ {
fmt::memory_buffer buffer; fmt::memory_buffer buffer;
format_to(buffer, "garbage"); format_to(fmt::appender(buffer), "garbage");
fmt::detail::format_error_code(buffer, 42, "test"); fmt::detail::format_error_code(buffer, 42, "test");
EXPECT_EQ("test: " + msg, to_string(buffer)); EXPECT_EQ("test: " + msg, to_string(buffer));
} }
{ {
fmt::memory_buffer buffer; fmt::memory_buffer buffer;
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size() + 1, auto prefix =
'x'); std::string(fmt::inline_buffer_size - msg.size() - sep.size() + 1, 'x');
fmt::detail::format_error_code(buffer, 42, prefix); fmt::detail::format_error_code(buffer, 42, prefix);
EXPECT_EQ(msg, to_string(buffer)); EXPECT_EQ(msg, to_string(buffer));
} }
@ -412,7 +324,8 @@ TEST(FormatTest, FormatErrorCode) {
// Test maximum buffer size. // Test maximum buffer size.
msg = fmt::format("error {}", codes[i]); msg = fmt::format("error {}", codes[i]);
fmt::memory_buffer buffer; fmt::memory_buffer buffer;
std::string prefix(fmt::inline_buffer_size - msg.size() - sep.size(), 'x'); auto prefix =
std::string(fmt::inline_buffer_size - msg.size() - sep.size(), 'x');
fmt::detail::format_error_code(buffer, codes[i], prefix); fmt::detail::format_error_code(buffer, codes[i], prefix);
EXPECT_EQ(prefix + sep + msg, to_string(buffer)); EXPECT_EQ(prefix + sep + msg, to_string(buffer));
size_t size = fmt::inline_buffer_size; size_t size = fmt::inline_buffer_size;
@ -425,9 +338,9 @@ TEST(FormatTest, FormatErrorCode) {
} }
} }
TEST(FormatTest, CountCodePoints) { TEST(format_impl_test, compute_width) {
EXPECT_EQ(4, EXPECT_EQ(4,
fmt::detail::count_code_points( fmt::detail::compute_width(
fmt::basic_string_view<fmt::detail::char8_type>( fmt::basic_string_view<fmt::detail::char8_type>(
reinterpret_cast<const fmt::detail::char8_type*>("ёжик")))); reinterpret_cast<const fmt::detail::char8_type*>("ёжик"))));
} }
@ -442,15 +355,26 @@ template <typename Int> void test_count_digits() {
} }
} }
TEST(UtilTest, CountDigits) { TEST(format_impl_test, count_digits) {
test_count_digits<uint32_t>(); test_count_digits<uint32_t>();
test_count_digits<uint64_t>(); test_count_digits<uint64_t>();
} }
TEST(UtilTest, WriteFallbackUIntPtr) { TEST(format_impl_test, write_fallback_uintptr) {
std::string s; std::string s;
fmt::detail::write_ptr<char>( fmt::detail::write_ptr<char>(
std::back_inserter(s), std::back_inserter(s),
fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr); fmt::detail::fallback_uintptr(reinterpret_cast<void*>(0xface)), nullptr);
EXPECT_EQ(s, "0xface"); EXPECT_EQ(s, "0xface");
} }
#ifdef _WIN32
# include <windows.h>
#endif
#ifdef _WIN32
TEST(format_impl_test, write_console_signature) {
decltype(WriteConsoleW)* p = fmt::detail::WriteConsoleW;
(void)p;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,30 @@
# Copyright (c) 2019, Paul Dreik # Copyright (c) 2019, Paul Dreik
# License: see LICENSE.rst in the fmt root directory # License: see LICENSE.rst in the fmt root directory
# settings this links in a main. useful for reproducing, # Link in the main function. Useful for reproducing, kcov, gdb, afl, valgrind.
# kcov, gdb, afl, valgrind. # (Note that libFuzzer can also reproduce, just pass it the files.)
# (note that libFuzzer can also reproduce, just pass it the files) option(FMT_FUZZ_LINKMAIN "Enables the reproduce mode, instead of libFuzzer" On)
option(FMT_FUZZ_LINKMAIN "enables the reproduce mode, instead of libFuzzer" On)
# For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for # For oss-fuzz - insert $LIB_FUZZING_ENGINE into the link flags, but only for
# the fuzz targets, otherwise the cmake configuration step fails. # the fuzz targets, otherwise the CMake configuration step fails.
set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets") set(FMT_FUZZ_LDFLAGS "" CACHE STRING "LDFLAGS for the fuzz targets")
# Find all fuzzers. # Adds a binary for reproducing, i.e. no fuzzing, just enables replaying data
set(SOURCES # through the fuzzers.
chrono_duration.cpp function(add_fuzzer source)
named_arg.cpp get_filename_component(basename ${source} NAME_WE)
one_arg.cpp set(name ${basename}-fuzzer)
sprintf.cpp add_executable(${name} ${source} fuzzer-common.h)
two_args.cpp
)
macro(implement_fuzzer sourcefile)
get_filename_component(basename ${sourcefile} NAME_WE)
set(name fuzzer_${basename})
add_executable(${name} ${sourcefile} fuzzer_common.h)
if (FMT_FUZZ_LINKMAIN) if (FMT_FUZZ_LINKMAIN)
target_sources(${name} PRIVATE main.cpp) target_sources(${name} PRIVATE main.cc)
endif () endif ()
target_link_libraries(${name} PRIVATE fmt) target_link_libraries(${name} PRIVATE fmt)
if (FMT_FUZZ_LDFLAGS) if (FMT_FUZZ_LDFLAGS)
target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS}) target_link_libraries(${name} PRIVATE ${FMT_FUZZ_LDFLAGS})
endif () endif ()
target_compile_features(${name} PRIVATE cxx_generic_lambdas) target_compile_features(${name} PRIVATE cxx_generic_lambdas)
endmacro () endfunction()
foreach (X IN ITEMS ${SOURCES}) foreach (source chrono-duration.cc float.cc named-arg.cc one-arg.cc two-args.cc)
implement_fuzzer(${X}) add_fuzzer(${source})
endforeach () endforeach ()

View File

@ -1,27 +1,4 @@
# FMT Fuzzer # Running the fuzzers locally
Fuzzing has revealed [several bugs](https://github.com/fmtlib/fmt/issues?&q=is%3Aissue+fuzz)
in fmt. It is a part of the continous fuzzing at
[oss-fuzz](https://github.com/google/oss-fuzz).
The source code is modified to make the fuzzing possible without locking up on
resource exhaustion:
```cpp
#ifdef FMT_FUZZ
if(spec.precision>100000) {
throw std::runtime_error("fuzz mode - avoiding large precision");
}
#endif
```
This macro `FMT_FUZZ` is enabled on OSS-Fuzz builds and makes fuzzing
practically possible. It is used in fmt code to prevent resource exhaustion in
fuzzing mode.
The macro `FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION` is the
defacto standard for making fuzzing practically possible to disable certain
fuzzing-unfriendly features (for example, randomness), see [the libFuzzer
documentation](https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode).
## Running the fuzzers locally
There is a [helper script](build.sh) to build the fuzzers, which has only been There is a [helper script](build.sh) to build the fuzzers, which has only been
tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on tested on Debian and Ubuntu linux so far. There should be no problems fuzzing on

View File

@ -1,7 +1,6 @@
#!/bin/sh #!/bin/sh
# #
# Creates fuzzer builds of various kinds # Creates fuzzer builds of various kinds
# - reproduce mode (no fuzzing, just enables replaying data through the fuzzers)
# - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works) # - oss-fuzz emulated mode (makes sure a simulated invocation by oss-fuzz works)
# - libFuzzer build (you will need clang) # - libFuzzer build (you will need clang)
# - afl build (you will need afl) # - afl build (you will need afl)
@ -9,7 +8,7 @@
# #
# Copyright (c) 2019 Paul Dreik # Copyright (c) 2019 Paul Dreik
# #
# License: see LICENSE.rst in the fmt root directory # For the license information refer to format.h.
set -e set -e
me=$(basename $0) me=$(basename $0)
@ -23,16 +22,7 @@ here=$(pwd)
CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g" CXXFLAGSALL="-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION= -g"
CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17" CMAKEFLAGSALL="$root -GNinja -DCMAKE_BUILD_TYPE=Debug -DFMT_DOC=Off -DFMT_TEST=Off -DFMT_FUZZ=On -DCMAKE_CXX_STANDARD=17"
#builds the fuzzers as one would do if using afl or just making # For performance analysis of the fuzzers.
#binaries for reproducing.
builddir=$here/build-fuzzers-reproduce
mkdir -p $builddir
cd $builddir
CXX="ccache g++" CXXFLAGS="$CXXFLAGSALL" cmake \
$CMAKEFLAGSALL
cmake --build $builddir
#for performance analysis of the fuzzers
builddir=$here/build-fuzzers-perfanalysis builddir=$here/build-fuzzers-perfanalysis
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
@ -43,7 +33,7 @@ $CMAKEFLAGSALL \
cmake --build $builddir cmake --build $builddir
#builds the fuzzers as oss-fuzz does # Builds the fuzzers as oss-fuzz does.
builddir=$here/build-fuzzers-ossfuzz builddir=$here/build-fuzzers-ossfuzz
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
@ -56,7 +46,7 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir cmake --build $builddir
#builds fuzzers for local fuzzing with libfuzzer with asan+usan # Builds fuzzers for local fuzzing with libfuzzer with asan+usan.
builddir=$here/build-fuzzers-libfuzzer builddir=$here/build-fuzzers-libfuzzer
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
@ -68,19 +58,7 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir cmake --build $builddir
#builds fuzzers for local fuzzing with libfuzzer with asan only # Builds a fast fuzzer for making coverage fast.
builddir=$here/build-fuzzers-libfuzzer-addr
mkdir -p $builddir
cd $builddir
CXX="clang++" \
CXXFLAGS="$CXXFLAGSALL -fsanitize=fuzzer-no-link,undefined" cmake \
cmake $CMAKEFLAGSALL \
-DFMT_FUZZ_LINKMAIN=Off \
-DFMT_FUZZ_LDFLAGS="-fsanitize=fuzzer"
cmake --build $builddir
#builds a fast fuzzer for making coverage fast
builddir=$here/build-fuzzers-fast builddir=$here/build-fuzzers-fast
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir
@ -94,7 +72,7 @@ cmake $CMAKEFLAGSALL \
cmake --build $builddir cmake --build $builddir
#builds fuzzers for local fuzzing with afl # Builds fuzzers for local fuzzing with afl.
builddir=$here/build-fuzzers-afl builddir=$here/build-fuzzers-afl
mkdir -p $builddir mkdir -p $builddir
cd $builddir cd $builddir

View File

@ -0,0 +1,135 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename Period, typename Rep>
void invoke_inner(fmt::string_view format_str, Rep rep) {
auto value = std::chrono::duration<Rep, Period>(rep);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, value);
#else
fmt::memory_buffer buf;
fmt::format_to(buf, format_str, value);
#endif
} catch (std::exception&) {
}
}
// Rep is a duration's representation type.
template <typename Rep>
void invoke_outer(const uint8_t* data, size_t size, int period) {
// Always use a fixed location of the data.
static_assert(sizeof(Rep) <= fixed_size, "fixed size is too small");
if (size <= fixed_size + 1) return;
const Rep rep = assign_from_buf<Rep>(data);
data += fixed_size;
size -= fixed_size;
// data is already allocated separately in libFuzzer so reading past the end
// will most likely be detected anyway.
const auto format_str = fmt::string_view(as_chars(data), size);
// yocto, zepto, zetta and yotta are not handled.
switch (period) {
case 1:
invoke_inner<std::atto>(format_str, rep);
break;
case 2:
invoke_inner<std::femto>(format_str, rep);
break;
case 3:
invoke_inner<std::pico>(format_str, rep);
break;
case 4:
invoke_inner<std::nano>(format_str, rep);
break;
case 5:
invoke_inner<std::micro>(format_str, rep);
break;
case 6:
invoke_inner<std::milli>(format_str, rep);
break;
case 7:
invoke_inner<std::centi>(format_str, rep);
break;
case 8:
invoke_inner<std::deci>(format_str, rep);
break;
case 9:
invoke_inner<std::deca>(format_str, rep);
break;
case 10:
invoke_inner<std::kilo>(format_str, rep);
break;
case 11:
invoke_inner<std::mega>(format_str, rep);
break;
case 12:
invoke_inner<std::giga>(format_str, rep);
break;
case 13:
invoke_inner<std::tera>(format_str, rep);
break;
case 14:
invoke_inner<std::peta>(format_str, rep);
break;
case 15:
invoke_inner<std::exa>(format_str, rep);
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 4) return 0;
const auto representation = data[0];
const auto period = data[1];
data += 2;
size -= 2;
switch (representation) {
case 1:
invoke_outer<char>(data, size, period);
break;
case 2:
invoke_outer<signed char>(data, size, period);
break;
case 3:
invoke_outer<unsigned char>(data, size, period);
break;
case 4:
invoke_outer<short>(data, size, period);
break;
case 5:
invoke_outer<unsigned short>(data, size, period);
break;
case 6:
invoke_outer<int>(data, size, period);
break;
case 7:
invoke_outer<unsigned int>(data, size, period);
break;
case 8:
invoke_outer<long>(data, size, period);
break;
case 9:
invoke_outer<unsigned long>(data, size, period);
break;
case 10:
invoke_outer<float>(data, size, period);
break;
case 11:
invoke_outer<double>(data, size, period);
break;
case 12:
invoke_outer<long double>(data, size, period);
break;
}
return 0;
}

View File

@ -0,0 +1,39 @@
// A fuzzer for floating-point formatter.
// For the license information refer to format.h.
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <limits>
#include <fmt/format.h>
#include "fuzzer-common.h"
void check_round_trip(fmt::string_view format_str, double value) {
auto buffer = fmt::memory_buffer();
fmt::format_to(buffer, format_str, value);
if (std::isnan(value)) {
auto nan = std::signbit(value) ? "-nan" : "nan";
if (fmt::string_view(buffer.data(), buffer.size()) != nan)
throw std::runtime_error("round trip failure");
return;
}
buffer.push_back('\0');
char* ptr = nullptr;
if (std::strtod(buffer.data(), &ptr) != value)
throw std::runtime_error("round trip failure");
if (ptr + 1 != buffer.end())
throw std::runtime_error("unparsed output");
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= sizeof(double) || !std::numeric_limits<double>::is_iec559)
return 0;
check_round_trip("{}", assign_from_buf<double>(data));
// A larger than necessary precision is used to trigger the fallback
// formatter.
check_round_trip("{:.50g}", assign_from_buf<double>(data));
return 0;
}

View File

@ -0,0 +1,75 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#ifndef FUZZER_COMMON_H
#define FUZZER_COMMON_H
#include <cstdint> // std::uint8_t
#include <cstring> // memcpy
#include <vector>
#include <fmt/core.h>
// One can format to either a string, or a buffer. The latter is faster, but
// one may be interested in formatting to a string instead to verify it works
// as intended. To avoid a combinatoric explosion, select this at compile time
// instead of dynamically from the fuzz data.
#define FMT_FUZZ_FORMAT_TO_STRING 0
// If {fmt} is given a buffer that is separately allocated, chances that address
// sanitizer detects out of bound reads is much higher. However, it slows down
// the fuzzing.
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
// The size of the largest possible type in use.
// To let the the fuzzer mutation be efficient at cross pollinating between
// different types, use a fixed size format. The same bit pattern, interpreted
// as another type, is likely interesting.
constexpr auto fixed_size = 16;
// Casts data to a char pointer.
template <typename T> inline const char* as_chars(const T* data) {
return reinterpret_cast<const char*>(data);
}
// Casts data to a byte pointer.
template <typename T> inline const std::uint8_t* as_bytes(const T* data) {
return reinterpret_cast<const std::uint8_t*>(data);
}
// Blits bytes from data to form an (assumed trivially constructible) object
// of type Item.
template <class Item> inline Item assign_from_buf(const std::uint8_t* data) {
auto item = Item();
std::memcpy(&item, data, sizeof(Item));
return item;
}
// Reads a boolean value by looking at the first byte from data.
template <> inline bool assign_from_buf<bool>(const std::uint8_t* data) {
return *data != 0;
}
struct data_to_string {
#if FMT_FUZZ_SEPARATE_ALLOCATION
std::vector<char> buffer;
data_to_string(const uint8_t* data, size_t size, bool add_terminator = false)
: buffer(size + (add_terminator ? 1 : 0)) {
std::memcpy(buffer.data(), data, size);
}
fmt::string_view get() const { return {buffer.data(), buffer.size()}; }
#else
fmt::string_view sv;
data_to_string(const uint8_t* data, size_t size, bool = false)
: str(as_chars(data), size) {}
fmt::string_view get() const { return sv; }
#endif
const char* data() const { return get().data(); }
};
#endif // FUZZER_COMMON_H

View File

@ -0,0 +1,22 @@
#include <cassert>
#include <fstream>
#include <vector>
#include "fuzzer-common.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size);
int main(int argc, char** argv) {
for (int i = 1; i < argc; ++i) {
std::ifstream in(argv[i]);
assert(in);
in.seekg(0, std::ios_base::end);
const auto size = in.tellg();
assert(size >= 0);
in.seekg(0, std::ios_base::beg);
std::vector<char> buf(static_cast<size_t>(size));
in.read(buf.data(), size);
assert(in.gcount() == size);
LLVMFuzzerTestOneInput(as_bytes(buf.data()), buf.size());
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <type_traits>
#include <vector>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename T>
void invoke_fmt(const uint8_t* data, size_t size, unsigned arg_name_size) {
static_assert(sizeof(T) <= fixed_size, "fixed_size too small");
if (size <= fixed_size) return;
const T value = assign_from_buf<T>(data);
data += fixed_size;
size -= fixed_size;
if (arg_name_size <= 0 || arg_name_size >= size) return;
data_to_string arg_name(data, arg_name_size, true);
data += arg_name_size;
size -= arg_name_size;
data_to_string format_str(data, size);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message =
fmt::format(format_str.get(), fmt::arg(arg_name.data(), value));
#else
fmt::memory_buffer out;
fmt::format_to(out, format_str.get(), fmt::arg(arg_name.data(), value));
#endif
} catch (std::exception&) {
}
}
// For dynamic dispatching to an explicit instantiation.
template <typename Callback> void invoke(int type, Callback callback) {
switch (type) {
case 0:
callback(bool());
break;
case 1:
callback(char());
break;
case 2:
using sc = signed char;
callback(sc());
break;
case 3:
using uc = unsigned char;
callback(uc());
break;
case 4:
callback(short());
break;
case 5:
using us = unsigned short;
callback(us());
break;
case 6:
callback(int());
break;
case 7:
callback(unsigned());
break;
case 8:
callback(long());
break;
case 9:
using ul = unsigned long;
callback(ul());
break;
case 10:
callback(float());
break;
case 11:
callback(double());
break;
case 12:
using LD = long double;
callback(LD());
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
// Switch types depending on the first byte of the input.
const auto type = data[0] & 0x0F;
const unsigned arg_name_size = (data[0] & 0xF0) >> 4;
data++;
size--;
invoke(type, [=](auto arg) {
invoke_fmt<decltype(arg)>(data, size, arg_name_size);
});
return 0;
}

View File

@ -0,0 +1,91 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <exception>
#include <fmt/chrono.h>
#include "fuzzer-common.h"
template <typename T, typename Repr>
const T* from_repr(const Repr& r) { return &r; }
template <>
const std::tm* from_repr<std::tm>(const std::time_t& t) {
return std::localtime(&t);
}
template <typename T, typename Repr = T>
void invoke_fmt(const uint8_t* data, size_t size) {
static_assert(sizeof(Repr) <= fixed_size, "Nfixed is too small");
if (size <= fixed_size) return;
auto repr = assign_from_buf<Repr>(data);
const T* value = from_repr<T>(repr);
if (!value) return;
data += fixed_size;
size -= fixed_size;
data_to_string format_str(data, size);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str.get(), *value);
#else
fmt::memory_buffer message;
fmt::format_to(message, format_str.get(), *value);
#endif
} catch (std::exception&) {
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
const auto first = data[0];
data++;
size--;
switch (first) {
case 0:
invoke_fmt<bool>(data, size);
break;
case 1:
invoke_fmt<char>(data, size);
break;
case 2:
invoke_fmt<unsigned char>(data, size);
break;
case 3:
invoke_fmt<signed char>(data, size);
break;
case 4:
invoke_fmt<short>(data, size);
break;
case 5:
invoke_fmt<unsigned short>(data, size);
break;
case 6:
invoke_fmt<int>(data, size);
break;
case 7:
invoke_fmt<unsigned int>(data, size);
break;
case 8:
invoke_fmt<long>(data, size);
break;
case 9:
invoke_fmt<unsigned long>(data, size);
break;
case 10:
invoke_fmt<float>(data, size);
break;
case 11:
invoke_fmt<double>(data, size);
break;
case 12:
invoke_fmt<long double>(data, size);
break;
case 13:
invoke_fmt<std::tm, std::time_t>(data, size);
break;
}
return 0;
}

View File

@ -0,0 +1,105 @@
// Copyright (c) 2019, Paul Dreik
// For the license information refer to format.h.
#include <cstdint>
#include <exception>
#include <string>
#include <fmt/format.h>
#include "fuzzer-common.h"
template <typename Item1, typename Item2>
void invoke_fmt(const uint8_t* data, size_t size) {
static_assert(sizeof(Item1) <= fixed_size, "size1 exceeded");
static_assert(sizeof(Item2) <= fixed_size, "size2 exceeded");
if (size <= fixed_size + fixed_size) return;
const Item1 item1 = assign_from_buf<Item1>(data);
data += fixed_size;
size -= fixed_size;
const Item2 item2 = assign_from_buf<Item2>(data);
data += fixed_size;
size -= fixed_size;
auto format_str = fmt::string_view(as_chars(data), size);
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, item1, item2);
#else
fmt::memory_buffer message;
fmt::format_to(message, format_str, item1, item2);
#endif
}
// For dynamic dispatching to an explicit instantiation.
template <typename Callback> void invoke(int index, Callback callback) {
switch (index) {
case 0:
callback(bool());
break;
case 1:
callback(char());
break;
case 2:
using sc = signed char;
callback(sc());
break;
case 3:
using uc = unsigned char;
callback(uc());
break;
case 4:
callback(short());
break;
case 5:
using us = unsigned short;
callback(us());
break;
case 6:
callback(int());
break;
case 7:
callback(unsigned());
break;
case 8:
callback(long());
break;
case 9:
using ul = unsigned long;
callback(ul());
break;
case 10:
callback(float());
break;
case 11:
callback(double());
break;
case 12:
using LD = long double;
callback(LD());
break;
case 13:
using ptr = void*;
callback(ptr());
break;
}
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size <= 3) return 0;
// Switch types depending on the first byte of the input.
const auto type1 = data[0] & 0x0F;
const auto type2 = (data[0] & 0xF0) >> 4;
data++;
size--;
try {
invoke(type1, [=](auto param1) {
invoke(type2, [=](auto param2) {
invoke_fmt<decltype(param1), decltype(param2)>(data, size);
});
});
} catch (std::exception&) {
}
return 0;
}

View File

@ -9,31 +9,18 @@
#include <gtest/gtest-spi.h> #include <gtest/gtest-spi.h>
#include <algorithm>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#if defined(_WIN32) && !defined(__MINGW32__) #include "fmt/os.h"
# include <crtdbg.h> // for _CrtSetReportMode
#endif // _WIN32
#include "util.h" #include "util.h"
namespace {
// This is used to suppress coverity warnings about untrusted values.
std::string sanitize(const std::string& s) {
std::string result;
for (std::string::const_iterator i = s.begin(), end = s.end(); i != end; ++i)
result.push_back(static_cast<char>(*i & 0xff));
return result;
}
// Tests that assertion macros evaluate their arguments exactly once. // Tests that assertion macros evaluate their arguments exactly once.
class SingleEvaluationTest : public ::testing::Test { namespace {
class single_evaluation_test : public ::testing::Test {
protected: protected:
SingleEvaluationTest() { single_evaluation_test() {
p_ = s_; p_ = s_;
a_ = 0; a_ = 0;
b_ = 0; b_ = 0;
@ -45,11 +32,12 @@ class SingleEvaluationTest : public ::testing::Test {
static int a_; static int a_;
static int b_; static int b_;
}; };
} // namespace
const char* const SingleEvaluationTest::s_ = "01234"; const char* const single_evaluation_test::s_ = "01234";
const char* SingleEvaluationTest::p_; const char* single_evaluation_test::p_;
int SingleEvaluationTest::a_; int single_evaluation_test::a_;
int SingleEvaluationTest::b_; int single_evaluation_test::b_;
void do_nothing() {} void do_nothing() {}
@ -61,7 +49,7 @@ FMT_NORETURN void throw_system_error() {
// Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument // Tests that when EXPECT_THROW_MSG fails, it evaluates its message argument
// exactly once. // exactly once.
TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) { TEST_F(single_evaluation_test, failed_expect_throw_msg) {
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234"); EXPECT_THROW_MSG(throw_exception(), std::exception, p_++), "01234");
EXPECT_EQ(s_ + 1, p_); EXPECT_EQ(s_ + 1, p_);
@ -69,14 +57,14 @@ TEST_F(SingleEvaluationTest, FailedEXPECT_THROW_MSG) {
// Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument // Tests that when EXPECT_SYSTEM_ERROR fails, it evaluates its message argument
// exactly once. // exactly once.
TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) { TEST_F(single_evaluation_test, failed_expect_system_error) {
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++), EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, p_++),
"01234"); "01234");
EXPECT_EQ(s_ + 1, p_); EXPECT_EQ(s_ + 1, p_);
} }
// Tests that assertion arguments are evaluated exactly once. // Tests that assertion arguments are evaluated exactly once.
TEST_F(SingleEvaluationTest, ExceptionTests) { TEST_F(single_evaluation_test, exception_tests) {
// successful EXPECT_THROW_MSG // successful EXPECT_THROW_MSG
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
{ // NOLINT { // NOLINT
@ -116,7 +104,7 @@ TEST_F(SingleEvaluationTest, ExceptionTests) {
EXPECT_EQ(4, b_); EXPECT_EQ(4, b_);
} }
TEST_F(SingleEvaluationTest, SystemErrorTests) { TEST_F(single_evaluation_test, system_error_tests) {
// successful EXPECT_SYSTEM_ERROR // successful EXPECT_SYSTEM_ERROR
EXPECT_SYSTEM_ERROR( EXPECT_SYSTEM_ERROR(
{ // NOLINT { // NOLINT
@ -159,14 +147,14 @@ TEST_F(SingleEvaluationTest, SystemErrorTests) {
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
// Tests that when EXPECT_WRITE fails, it evaluates its message argument // Tests that when EXPECT_WRITE fails, it evaluates its message argument
// exactly once. // exactly once.
TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) { TEST_F(single_evaluation_test, failed_expect_write) {
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++), EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
"01234"); "01234");
EXPECT_EQ(s_ + 1, p_); EXPECT_EQ(s_ + 1, p_);
} }
// Tests that assertion arguments are evaluated exactly once. // Tests that assertion arguments are evaluated exactly once.
TEST_F(SingleEvaluationTest, WriteTests) { TEST_F(single_evaluation_test, write_tests) {
// successful EXPECT_WRITE // successful EXPECT_WRITE
EXPECT_WRITE( EXPECT_WRITE(
stdout, stdout,
@ -192,7 +180,7 @@ TEST_F(SingleEvaluationTest, WriteTests) {
} }
// Tests EXPECT_WRITE. // Tests EXPECT_WRITE.
TEST(ExpectTest, EXPECT_WRITE) { TEST(gtest_extra_test, expect_write) {
EXPECT_WRITE(stdout, do_nothing(), ""); EXPECT_WRITE(stdout, do_nothing(), "");
EXPECT_WRITE(stdout, std::printf("test"), "test"); EXPECT_WRITE(stdout, std::printf("test"), "test");
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test"); EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
@ -201,7 +189,7 @@ TEST(ExpectTest, EXPECT_WRITE) {
" Actual: that"); " Actual: that");
} }
TEST(StreamingAssertionsTest, EXPECT_WRITE) { TEST(gtest_extra_test, expect_write_streaming) {
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure"; EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other") EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
<< "expected failure", << "expected failure",
@ -211,7 +199,7 @@ TEST(StreamingAssertionsTest, EXPECT_WRITE) {
// Tests that the compiler will not complain about unreachable code in the // Tests that the compiler will not complain about unreachable code in the
// EXPECT_THROW_MSG macro. // EXPECT_THROW_MSG macro.
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) { TEST(gtest_extra_test, expect_throw_no_unreachable_code_warning) {
int n = 0; int n = 0;
using std::runtime_error; using std::runtime_error;
EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, ""); EXPECT_THROW_MSG(throw runtime_error(""), runtime_error, "");
@ -223,7 +211,7 @@ TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
// Tests that the compiler will not complain about unreachable code in the // Tests that the compiler will not complain about unreachable code in the
// EXPECT_SYSTEM_ERROR macro. // EXPECT_SYSTEM_ERROR macro.
TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) { TEST(gtest_extra_test, expect_system_error_no_unreachable_code_warning) {
int n = 0; int n = 0;
EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test"); EXPECT_SYSTEM_ERROR(throw fmt::system_error(EDOM, "test"), EDOM, "test");
EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), ""); EXPECT_NONFATAL_FAILURE(EXPECT_SYSTEM_ERROR(n++, EDOM, ""), "");
@ -233,7 +221,7 @@ TEST(ExpectSystemErrorTest, DoesNotGenerateUnreachableCodeWarning) {
""); "");
} }
TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) { TEST(gtest_extra_test, expect_throw_behaves_like_single_statement) {
if (::testing::internal::AlwaysFalse()) if (::testing::internal::AlwaysFalse())
EXPECT_THROW_MSG(do_nothing(), std::exception, ""); EXPECT_THROW_MSG(do_nothing(), std::exception, "");
@ -243,7 +231,7 @@ TEST(AssertionSyntaxTest, ExceptionAssertionBehavesLikeSingleStatement) {
do_nothing(); do_nothing();
} }
TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) { TEST(gtest_extra_test, expect_system_error_behaves_like_single_statement) {
if (::testing::internal::AlwaysFalse()) if (::testing::internal::AlwaysFalse())
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, ""); EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "");
@ -253,7 +241,7 @@ TEST(AssertionSyntaxTest, SystemErrorAssertionBehavesLikeSingleStatement) {
do_nothing(); do_nothing();
} }
TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) { TEST(gtest_extra_test, expect_write_behaves_like_single_statement) {
if (::testing::internal::AlwaysFalse()) if (::testing::internal::AlwaysFalse())
EXPECT_WRITE(stdout, std::printf("x"), "x"); EXPECT_WRITE(stdout, std::printf("x"), "x");
@ -264,7 +252,7 @@ TEST(AssertionSyntaxTest, WriteAssertionBehavesLikeSingleStatement) {
} }
// Tests EXPECT_THROW_MSG. // Tests EXPECT_THROW_MSG.
TEST(ExpectTest, EXPECT_THROW_MSG) { TEST(gtest_extra_test, expect_throw_msg) {
EXPECT_THROW_MSG(throw_exception(), std::exception, "test"); EXPECT_THROW_MSG(throw_exception(), std::exception, "test");
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"), EXPECT_THROW_MSG(throw_exception(), std::logic_error, "test"),
@ -282,15 +270,15 @@ TEST(ExpectTest, EXPECT_THROW_MSG) {
} }
// Tests EXPECT_SYSTEM_ERROR. // Tests EXPECT_SYSTEM_ERROR.
TEST(ExpectTest, EXPECT_SYSTEM_ERROR) { TEST(gtest_extra_test, expect_system_error) {
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test"); EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test");
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"), EXPECT_SYSTEM_ERROR(throw_exception(), EDOM, "test"),
"Expected: throw_exception() throws an exception of " "Expected: throw_exception() throws an exception of "
"type fmt::system_error.\n Actual: it throws a different type."); "type std::system_error.\n Actual: it throws a different type.");
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"), EXPECT_SYSTEM_ERROR(do_nothing(), EDOM, "test"),
"Expected: do_nothing() throws an exception of type fmt::system_error.\n" "Expected: do_nothing() throws an exception of type std::system_error.\n"
" Actual: it throws nothing."); " Actual: it throws nothing.");
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"), EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "other"),
@ -298,11 +286,11 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
"throw_system_error() throws an exception with a different message.\n" "throw_system_error() throws an exception with a different message.\n"
"Expected: {}\n" "Expected: {}\n"
" Actual: {}", " Actual: {}",
format_system_error(EDOM, "other"), system_error_message(EDOM, "other"),
format_system_error(EDOM, "test"))); system_error_message(EDOM, "test")));
} }
TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) { TEST(gtest_extra_test, expect_throw_msg_streaming) {
EXPECT_THROW_MSG(throw_exception(), std::exception, "test") EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
<< "unexpected failure"; << "unexpected failure";
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
@ -311,7 +299,7 @@ TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
"expected failure"); "expected failure");
} }
TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) { TEST(gtest_extra_test, expect_system_error_streaming) {
EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test") EXPECT_SYSTEM_ERROR(throw_system_error(), EDOM, "test")
<< "unexpected failure"; << "unexpected failure";
EXPECT_NONFATAL_FAILURE( EXPECT_NONFATAL_FAILURE(
@ -320,31 +308,19 @@ TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
"expected failure"); "expected failure");
} }
TEST(UtilTest, FormatSystemError) {
fmt::memory_buffer out;
fmt::format_system_error(out, EDOM, "test message");
EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
}
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
using fmt::buffered_file; using fmt::buffered_file;
using fmt::error_code;
using fmt::file; using fmt::file;
TEST(ErrorCodeTest, Ctor) { TEST(output_redirect_test, scoped_redirect) {
EXPECT_EQ(error_code().get(), 0);
EXPECT_EQ(error_code(42).get(), 42);
}
TEST(OutputRedirectTest, ScopedRedirect) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
{ {
buffered_file file(write_end.fdopen("w")); buffered_file file(write_end.fdopen("w"));
std::fprintf(file.get(), "[[["); std::fprintf(file.get(), "[[[");
{ {
OutputRedirect redir(file.get()); output_redirect redir(file.get());
std::fprintf(file.get(), "censored"); std::fprintf(file.get(), "censored");
} }
std::fprintf(file.get(), "]]]"); std::fprintf(file.get(), "]]]");
@ -352,8 +328,8 @@ TEST(OutputRedirectTest, ScopedRedirect) {
EXPECT_READ(read_end, "[[[]]]"); EXPECT_READ(read_end, "[[[]]]");
} }
// Test that OutputRedirect handles errors in flush correctly. // Test that output_redirect handles errors in flush correctly.
TEST(OutputRedirectTest, FlushErrorInCtor) { TEST(output_redirect_test, flush_error_in_ctor) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
int write_fd = write_end.descriptor(); int write_fd = write_end.descriptor();
@ -362,47 +338,47 @@ TEST(OutputRedirectTest, FlushErrorInCtor) {
// Put a character in a file buffer. // Put a character in a file buffer.
EXPECT_EQ('x', fputc('x', f.get())); EXPECT_EQ('x', fputc('x', f.get()));
FMT_POSIX(close(write_fd)); FMT_POSIX(close(write_fd));
std::unique_ptr<OutputRedirect> redir{nullptr}; std::unique_ptr<output_redirect> redir{nullptr};
EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new OutputRedirect(f.get())), EBADF, EXPECT_SYSTEM_ERROR_NOASSERT(redir.reset(new output_redirect(f.get())), EBADF,
"cannot flush stream"); "cannot flush stream");
redir.reset(nullptr); redir.reset(nullptr);
write_copy.dup2(write_fd); // "undo" close or dtor will fail write_copy.dup2(write_fd); // "undo" close or dtor will fail
} }
TEST(OutputRedirectTest, DupErrorInCtor) { TEST(output_redirect_test, dup_error_in_ctor) {
buffered_file f = open_buffered_file(); buffered_file f = open_buffered_file();
int fd = (f.fileno)(); int fd = (f.fileno)();
file copy = file::dup(fd); file copy = file::dup(fd);
FMT_POSIX(close(fd)); FMT_POSIX(close(fd));
std::unique_ptr<OutputRedirect> redir{nullptr}; std::unique_ptr<output_redirect> redir{nullptr};
EXPECT_SYSTEM_ERROR_NOASSERT( EXPECT_SYSTEM_ERROR_NOASSERT(
redir.reset(new OutputRedirect(f.get())), EBADF, redir.reset(new output_redirect(f.get())), EBADF,
fmt::format("cannot duplicate file descriptor {}", fd)); fmt::format("cannot duplicate file descriptor {}", fd));
copy.dup2(fd); // "undo" close or dtor will fail copy.dup2(fd); // "undo" close or dtor will fail
} }
TEST(OutputRedirectTest, RestoreAndRead) { TEST(output_redirect_test, restore_and_read) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
buffered_file file(write_end.fdopen("w")); buffered_file file(write_end.fdopen("w"));
std::fprintf(file.get(), "[[["); std::fprintf(file.get(), "[[[");
OutputRedirect redir(file.get()); output_redirect redir(file.get());
std::fprintf(file.get(), "censored"); std::fprintf(file.get(), "censored");
EXPECT_EQ("censored", sanitize(redir.restore_and_read())); EXPECT_EQ("censored", redir.restore_and_read());
EXPECT_EQ("", sanitize(redir.restore_and_read())); EXPECT_EQ("", redir.restore_and_read());
std::fprintf(file.get(), "]]]"); std::fprintf(file.get(), "]]]");
file = buffered_file(); file = buffered_file();
EXPECT_READ(read_end, "[[[]]]"); EXPECT_READ(read_end, "[[[]]]");
} }
// Test that OutputRedirect handles errors in flush correctly. // Test that OutputRedirect handles errors in flush correctly.
TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) { TEST(output_redirect_test, flush_error_in_restore_and_read) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
int write_fd = write_end.descriptor(); int write_fd = write_end.descriptor();
file write_copy = write_end.dup(write_fd); file write_copy = write_end.dup(write_fd);
buffered_file f = write_end.fdopen("w"); buffered_file f = write_end.fdopen("w");
OutputRedirect redir(f.get()); output_redirect redir(f.get());
// Put a character in a file buffer. // Put a character in a file buffer.
EXPECT_EQ('x', fputc('x', f.get())); EXPECT_EQ('x', fputc('x', f.get()));
FMT_POSIX(close(write_fd)); FMT_POSIX(close(write_fd));
@ -411,13 +387,13 @@ TEST(OutputRedirectTest, FlushErrorInRestoreAndRead) {
write_copy.dup2(write_fd); // "undo" close or dtor will fail write_copy.dup2(write_fd); // "undo" close or dtor will fail
} }
TEST(OutputRedirectTest, ErrorInDtor) { TEST(output_redirect_test, error_in_dtor) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
int write_fd = write_end.descriptor(); int write_fd = write_end.descriptor();
file write_copy = write_end.dup(write_fd); file write_copy = write_end.dup(write_fd);
buffered_file f = write_end.fdopen("w"); buffered_file f = write_end.fdopen("w");
std::unique_ptr<OutputRedirect> redir(new OutputRedirect(f.get())); std::unique_ptr<output_redirect> redir(new output_redirect(f.get()));
// Put a character in a file buffer. // Put a character in a file buffer.
EXPECT_EQ('x', fputc('x', f.get())); EXPECT_EQ('x', fputc('x', f.get()));
EXPECT_WRITE( EXPECT_WRITE(
@ -430,10 +406,8 @@ TEST(OutputRedirectTest, ErrorInDtor) {
FMT_POSIX(close(write_fd)); FMT_POSIX(close(write_fd));
SUPPRESS_ASSERT(redir.reset(nullptr)); SUPPRESS_ASSERT(redir.reset(nullptr));
}, },
format_system_error(EBADF, "cannot flush stream")); system_error_message(EBADF, "cannot flush stream"));
write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail write_copy.dup2(write_fd); // "undo" close or dtor of buffered_file will fail
} }
#endif // FMT_USE_FILE_DESCRIPTORS #endif // FMT_USE_FCNTL
} // namespace

View File

@ -11,24 +11,7 @@
using fmt::file; using fmt::file;
void OutputRedirect::flush() { output_redirect::output_redirect(FILE* f) : file_(f) {
# if EOF != -1
# error "FMT_RETRY assumes return value of -1 indicating failure"
# endif
int result = 0;
FMT_RETRY(result, fflush(file_));
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
}
void OutputRedirect::restore() {
if (original_.descriptor() == -1) return; // Already restored.
flush();
// Restore the original file.
original_.dup2(FMT_POSIX(fileno(file_)));
original_.close();
}
OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
flush(); flush();
int fd = FMT_POSIX(fileno(f)); int fd = FMT_POSIX(fileno(f));
// Create a file object referring to the original file. // Create a file object referring to the original file.
@ -40,7 +23,7 @@ OutputRedirect::OutputRedirect(FILE* f) : file_(f) {
write_end.dup2(fd); write_end.dup2(fd);
} }
OutputRedirect::~OutputRedirect() FMT_NOEXCEPT { output_redirect::~output_redirect() FMT_NOEXCEPT {
try { try {
restore(); restore();
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -48,7 +31,24 @@ OutputRedirect::~OutputRedirect() FMT_NOEXCEPT {
} }
} }
std::string OutputRedirect::restore_and_read() { void output_redirect::flush() {
# if EOF != -1
# error "FMT_RETRY assumes return value of -1 indicating failure"
# endif
int result = 0;
FMT_RETRY(result, fflush(file_));
if (result != 0) throw fmt::system_error(errno, "cannot flush stream");
}
void output_redirect::restore() {
if (original_.descriptor() == -1) return; // Already restored.
flush();
// Restore the original file.
original_.dup2(FMT_POSIX(fileno(file_)));
original_.close();
}
std::string output_redirect::restore_and_read() {
// Restore output. // Restore output.
restore(); restore();
@ -79,9 +79,3 @@ std::string read(file& f, size_t count) {
} }
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
std::string format_system_error(int error_code, fmt::string_view message) {
fmt::memory_buffer out;
format_system_error(out, error_code, message);
return to_string(out);
}

View File

@ -8,10 +8,12 @@
#ifndef FMT_GTEST_EXTRA_H_ #ifndef FMT_GTEST_EXTRA_H_
#define FMT_GTEST_EXTRA_H_ #define FMT_GTEST_EXTRA_H_
#include <stdlib.h> // _invalid_parameter_handler
#include <string> #include <string>
#include "fmt/os.h" #include "fmt/os.h"
#include "gmock.h" #include "gmock/gmock.h"
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \ #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
@ -51,30 +53,35 @@
FMT_TEST_THROW_(statement, expected_exception, expected_message, \ FMT_TEST_THROW_(statement, expected_exception, expected_message, \
GTEST_NONFATAL_FAILURE_) GTEST_NONFATAL_FAILURE_)
std::string format_system_error(int error_code, fmt::string_view message); inline std::string system_error_message(int error_code,
const std::string& message) {
auto ec = std::error_code(error_code, std::generic_category());
return std::system_error(ec, message).what();
}
#define EXPECT_SYSTEM_ERROR(statement, error_code, message) \ #define EXPECT_SYSTEM_ERROR(statement, error_code, message) \
EXPECT_THROW_MSG(statement, fmt::system_error, \ EXPECT_THROW_MSG(statement, std::system_error, \
format_system_error(error_code, message)) system_error_message(error_code, message))
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
// Captures file output by redirecting it to a pipe. // Captures file output by redirecting it to a pipe.
// The output it can handle is limited by the pipe capacity. // The output it can handle is limited by the pipe capacity.
class OutputRedirect { class output_redirect {
private: private:
FILE* file_; FILE* file_;
fmt::file original_; // Original file passed to redirector. fmt::file original_; // Original file passed to redirector.
fmt::file read_end_; // Read end of the pipe where the output is redirected. fmt::file read_end_; // Read end of the pipe where the output is redirected.
GTEST_DISALLOW_COPY_AND_ASSIGN_(OutputRedirect);
void flush(); void flush();
void restore(); void restore();
public: public:
explicit OutputRedirect(FILE* file); explicit output_redirect(FILE* file);
~OutputRedirect() FMT_NOEXCEPT; ~output_redirect() FMT_NOEXCEPT;
output_redirect(const output_redirect&) = delete;
void operator=(const output_redirect&) = delete;
// Restores the original file, reads output from the pipe into a string // Restores the original file, reads output from the pipe into a string
// and returns it. // and returns it.
@ -85,7 +92,7 @@ class OutputRedirect {
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \ if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \
std::string gtest_expected_output = expected_output; \ std::string gtest_expected_output = expected_output; \
OutputRedirect gtest_redir(file); \ output_redirect gtest_redir(file); \
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
std::string gtest_output = gtest_redir.restore_and_read(); \ std::string gtest_output = gtest_redir.restore_and_read(); \
if (gtest_output != gtest_expected_output) { \ if (gtest_output != gtest_expected_output) { \
@ -106,7 +113,7 @@ class OutputRedirect {
// Suppresses Windows assertions on invalid file descriptors, making // Suppresses Windows assertions on invalid file descriptors, making
// POSIX functions return proper error codes instead of crashing on Windows. // POSIX functions return proper error codes instead of crashing on Windows.
class SuppressAssert { class suppress_assert {
private: private:
_invalid_parameter_handler original_handler_; _invalid_parameter_handler original_handler_;
int original_report_mode_; int original_report_mode_;
@ -115,11 +122,11 @@ class SuppressAssert {
const wchar_t*, unsigned, uintptr_t) {} const wchar_t*, unsigned, uintptr_t) {}
public: public:
SuppressAssert() suppress_assert()
: original_handler_( : original_handler_(
_set_invalid_parameter_handler(handle_invalid_parameter)), _set_invalid_parameter_handler(handle_invalid_parameter)),
original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {} original_report_mode_(_CrtSetReportMode(_CRT_ASSERT, 0)) {}
~SuppressAssert() { ~suppress_assert() {
_set_invalid_parameter_handler(original_handler_); _set_invalid_parameter_handler(original_handler_);
_CrtSetReportMode(_CRT_ASSERT, original_report_mode_); _CrtSetReportMode(_CRT_ASSERT, original_report_mode_);
} }
@ -127,7 +134,7 @@ class SuppressAssert {
# define SUPPRESS_ASSERT(statement) \ # define SUPPRESS_ASSERT(statement) \
{ \ { \
SuppressAssert sa; \ suppress_assert sa; \
statement; \ statement; \
} }
# else # else
@ -145,12 +152,13 @@ std::string read(fmt::file& f, size_t count);
read(file, fmt::string_view(expected_content).size())) read(file, fmt::string_view(expected_content).size()))
#else #else
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED() # define EXPECT_WRITE(file, statement, expected_output) \
do { \
(void)(file); \
(void)(statement); \
(void)(expected_output); \
SUCCEED(); \
} while (false)
#endif // FMT_USE_FCNTL #endif // FMT_USE_FCNTL
template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
ScopedMock() { Mock::instance = this; }
~ScopedMock() { Mock::instance = nullptr; }
};
#endif // FMT_GTEST_EXTRA_H_ #endif // FMT_GTEST_EXTRA_H_

View File

@ -0,0 +1,3 @@
# Disable clang-format here
DisableFormat: true
SortIncludes: Never

View File

@ -0,0 +1,31 @@
#------------------------------------------------------------------------------
# Build the google test library
# We compile Google Test ourselves instead of using pre-compiled libraries.
# See the Google Test FAQ "Why is it not recommended to install a
# pre-compiled copy of Google Test (for example, into /usr/local)?"
# at http://code.google.com/p/googletest/wiki/FAQ for more details.
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 .)
find_package(Threads)
if (Threads_FOUND)
target_link_libraries(gtest ${CMAKE_THREAD_LIBS_INIT})
else ()
target_compile_definitions(gtest PUBLIC GTEST_HAS_PTHREAD=0)
endif ()
if (MSVC)
# Disable MSVC warnings of _CRT_INSECURE_DEPRECATE functions.
target_compile_definitions(gtest PRIVATE _CRT_SECURE_NO_WARNINGS)
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Disable MSVC warnings of POSIX functions.
target_compile_options(gtest PUBLIC -Wno-deprecated-declarations)
endif ()
endif ()
# Silence MSVC tr1 deprecation warning in gmock.
target_compile_definitions(gtest
PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
// Copyright 2007, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Utilities for testing Google Test itself and code that uses Google Test
// (e.g. frameworks built on top of Google Test).
// GOOGLETEST_CM0004 DO NOT DELETE
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
#include "gtest/gtest.h"
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
/* class A needs to have dll-interface to be used by clients of class B */)
namespace testing {
// This helper class can be used to mock out Google Test failure reporting
// so that we can test Google Test or code that builds on Google Test.
//
// An object of this class appends a TestPartResult object to the
// TestPartResultArray object given in the constructor whenever a Google Test
// failure is reported. It can either intercept only failures that are
// generated in the same thread that created this object or it can intercept
// all generated failures. The scope of this mock object can be controlled with
// the second argument to the two arguments constructor.
class GTEST_API_ ScopedFakeTestPartResultReporter
: public TestPartResultReporterInterface {
public:
// The two possible mocking modes of this object.
enum InterceptMode {
INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures.
INTERCEPT_ALL_THREADS // Intercepts all failures.
};
// The c'tor sets this object as the test part result reporter used
// by Google Test. The 'result' parameter specifies where to report the
// results. This reporter will only catch failures generated in the current
// thread. DEPRECATED
explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
// Same as above, but you can choose the interception scope of this object.
ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
TestPartResultArray* result);
// The d'tor restores the previous test part result reporter.
~ScopedFakeTestPartResultReporter() override;
// Appends the TestPartResult object to the TestPartResultArray
// received in the constructor.
//
// This method is from the TestPartResultReporterInterface
// interface.
void ReportTestPartResult(const TestPartResult& result) override;
private:
void Init();
const InterceptMode intercept_mode_;
TestPartResultReporterInterface* old_reporter_;
TestPartResultArray* const result_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter);
};
namespace internal {
// A helper class for implementing EXPECT_FATAL_FAILURE() and
// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given
// TestPartResultArray contains exactly one failure that has the given
// type and contains the given substring. If that's not the case, a
// non-fatal failure will be generated.
class GTEST_API_ SingleFailureChecker {
public:
// The constructor remembers the arguments.
SingleFailureChecker(const TestPartResultArray* results,
TestPartResult::Type type, const std::string& substr);
~SingleFailureChecker();
private:
const TestPartResultArray* const results_;
const TestPartResult::Type type_;
const std::string substr_;
GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker);
};
} // namespace internal
} // namespace testing
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
// A set of macros for testing Google Test assertions or code that's expected
// to generate Google Test fatal failures. It verifies that the given
// statement will cause exactly one fatal Google Test failure with 'substr'
// being part of the failure message.
//
// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
// affects and considers failures generated in the current thread and
// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
//
// The verification of the assertion is done correctly even when the statement
// throws an exception or aborts the current function.
//
// Known restrictions:
// - 'statement' cannot reference local non-static variables or
// non-static members of the current object.
// - 'statement' cannot return a value.
// - You cannot stream a failure message to this macro.
//
// Note that even though the implementations of the following two
// macros are much alike, we cannot refactor them to use a common
// helper macro, due to some peculiarity in how the preprocessor
// works. The AcceptsMacroThatExpandsToUnprotectedComma test in
// gtest_unittest.cc will fail to compile if we do that.
#define EXPECT_FATAL_FAILURE(statement, substr) \
do { \
class GTestExpectFatalFailureHelper {\
public:\
static void Execute() { statement; }\
};\
::testing::TestPartResultArray gtest_failures;\
::testing::internal::SingleFailureChecker gtest_checker(\
&gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
{\
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
::testing::ScopedFakeTestPartResultReporter:: \
INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
GTestExpectFatalFailureHelper::Execute();\
}\
} while (::testing::internal::AlwaysFalse())
#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
do { \
class GTestExpectFatalFailureHelper {\
public:\
static void Execute() { statement; }\
};\
::testing::TestPartResultArray gtest_failures;\
::testing::internal::SingleFailureChecker gtest_checker(\
&gtest_failures, ::testing::TestPartResult::kFatalFailure, (substr));\
{\
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
::testing::ScopedFakeTestPartResultReporter:: \
INTERCEPT_ALL_THREADS, &gtest_failures);\
GTestExpectFatalFailureHelper::Execute();\
}\
} while (::testing::internal::AlwaysFalse())
// A macro for testing Google Test assertions or code that's expected to
// generate Google Test non-fatal failures. It asserts that the given
// statement will cause exactly one non-fatal Google Test failure with 'substr'
// being part of the failure message.
//
// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
// affects and considers failures generated in the current thread and
// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
//
// 'statement' is allowed to reference local variables and members of
// the current object.
//
// The verification of the assertion is done correctly even when the statement
// throws an exception or aborts the current function.
//
// Known restrictions:
// - You cannot stream a failure message to this macro.
//
// Note that even though the implementations of the following two
// macros are much alike, we cannot refactor them to use a common
// helper macro, due to some peculiarity in how the preprocessor
// works. If we do that, the code won't compile when the user gives
// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
// expands to code containing an unprotected comma. The
// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
// catches that.
//
// For the same reason, we have to write
// if (::testing::internal::AlwaysTrue()) { statement; }
// instead of
// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
// to avoid an MSVC warning on unreachable code.
#define EXPECT_NONFATAL_FAILURE(statement, substr) \
do {\
::testing::TestPartResultArray gtest_failures;\
::testing::internal::SingleFailureChecker gtest_checker(\
&gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
(substr));\
{\
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
::testing::ScopedFakeTestPartResultReporter:: \
INTERCEPT_ONLY_CURRENT_THREAD, &gtest_failures);\
if (::testing::internal::AlwaysTrue()) { statement; }\
}\
} while (::testing::internal::AlwaysFalse())
#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
do {\
::testing::TestPartResultArray gtest_failures;\
::testing::internal::SingleFailureChecker gtest_checker(\
&gtest_failures, ::testing::TestPartResult::kNonFatalFailure, \
(substr));\
{\
::testing::ScopedFakeTestPartResultReporter gtest_reporter(\
::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
&gtest_failures);\
if (::testing::internal::AlwaysTrue()) { statement; }\
}\
} while (::testing::internal::AlwaysFalse())
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,7 @@
// Header-only configuration test // Header-only configuration test
#include "fmt/core.h" #include "fmt/core.h"
#ifndef FMT_HEADER_ONLY
# error "Not in the header-only mode."
#endif

View File

@ -8,14 +8,18 @@
#ifndef FMT_MOCK_ALLOCATOR_H_ #ifndef FMT_MOCK_ALLOCATOR_H_
#define FMT_MOCK_ALLOCATOR_H_ #define FMT_MOCK_ALLOCATOR_H_
#include "fmt/format.h" #include <assert.h> // assert
#include "gmock.h" #include <stddef.h> // size_t
#include <memory> // std::allocator_traits
#include "gmock/gmock.h"
template <typename T> class mock_allocator { template <typename T> class mock_allocator {
public: public:
mock_allocator() {} mock_allocator() {}
mock_allocator(const mock_allocator&) {} mock_allocator(const mock_allocator&) {}
typedef T value_type; using value_type = T;
MOCK_METHOD1_T(allocate, T*(size_t n)); MOCK_METHOD1_T(allocate, T*(size_t n));
MOCK_METHOD2_T(deallocate, void(T* p, size_t n)); MOCK_METHOD2_T(deallocate, void(T* p, size_t n));
}; };
@ -30,7 +34,7 @@ template <typename Allocator> class allocator_ref {
} }
public: public:
typedef typename Allocator::value_type value_type; using value_type = typename Allocator::value_type;
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {} explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}

View File

@ -0,0 +1,565 @@
// Formatting library for C++ - module tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
//
// Copyright (c) 2021 - present, Daniela Engert
// All Rights Reserved
// {fmt} module.
#ifdef _MSC_FULL_VER
// hide some implementation bugs in msvc
// that are not essential to users of the module.
# define FMT_HIDE_MODULE_BUGS
#endif
#include <bit>
#include <chrono>
#include <exception>
#include <iterator>
#include <locale>
#include <memory>
#include <ostream>
#include <string>
#include <string_view>
#include <system_error>
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h>
# define FMT_USE_FCNTL 1
#else
# define FMT_USE_FCNTL 0
#endif
#define FMT_NOEXCEPT noexcept
import fmt;
// check for macros leaking from BMI
static bool macro_leaked =
#if defined(FMT_CORE_H_) || defined(FMT_FORMAT_H)
true;
#else
false;
#endif
#include "gtest-extra.h"
// an implicitly exported namespace must be visible [module.interface]/2.2
TEST(module_test, namespace) {
using namespace fmt;
using namespace fmt::literals;
ASSERT_TRUE(true);
}
namespace detail {
bool oops_detail_namespace_is_visible;
}
namespace fmt {
bool namespace_detail_invisible() {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
_MSC_FULL_VER <= 192930129
// bug in msvc up to 16.11-pre1:
// the namespace is visible even when it is neither
// implicitly nor explicitly exported
return true;
#else
using namespace detail;
// this fails to compile if fmt::detail is visible
return !oops_detail_namespace_is_visible;
#endif
}
} // namespace fmt
// the non-exported namespace 'detail' must be invisible [module.interface]/2
TEST(module_test, detail_namespace) {
EXPECT_TRUE(fmt::namespace_detail_invisible());
}
// macros must not be imported from a *named* module [cpp.import]/5.1
TEST(module_test, macros) {
#if defined(FMT_HIDE_MODULE_BUGS) && defined(_MSC_FULL_VER) && \
_MSC_FULL_VER <= 192930129
// bug in msvc up to 16.11-pre1:
// include-guard macros leak from BMI
// and even worse: they cannot be #undef-ined
macro_leaked = false;
#endif
EXPECT_FALSE(macro_leaked);
}
// The following is less about functional testing (that's done elsewhere)
// 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.
// Instantiate as many code paths as possible.
TEST(module_test, to_string) {
EXPECT_EQ("42", fmt::to_string(42));
EXPECT_EQ("42", fmt::to_string(42.0));
EXPECT_EQ(L"42", fmt::to_wstring(42));
EXPECT_EQ(L"42", fmt::to_wstring(42.0));
}
TEST(module_test, format) {
EXPECT_EQ("42", fmt::format("{:}", 42));
EXPECT_EQ("-42", fmt::format("{0}", -42.0));
EXPECT_EQ(L"42", fmt::format(L"{:}", 42));
EXPECT_EQ(L"-42", fmt::format(L"{0}", -42.0));
}
TEST(module_test, format_to) {
std::string s;
fmt::format_to(std::back_inserter(s), "{}", 42);
EXPECT_EQ("42", s);
char buffer[4] = {0};
fmt::format_to(buffer, "{}", 42);
EXPECT_EQ("42", std::string_view(buffer));
fmt::memory_buffer mb;
fmt::format_to(mb, "{}", 42);
EXPECT_EQ("42", std::string_view(buffer));
std::wstring w;
fmt::format_to(std::back_inserter(w), L"{}", 42);
EXPECT_EQ(L"42", w);
wchar_t wbuffer[4] = {0};
fmt::format_to(wbuffer, L"{}", 42);
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
fmt::wmemory_buffer wb;
fmt::format_to(wb, L"{}", 42);
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
}
TEST(module_test, formatted_size) {
EXPECT_EQ(2u, fmt::formatted_size("{}", 42));
EXPECT_EQ(2u, fmt::formatted_size(L"{}", 42));
}
TEST(module_test, format_to_n) {
std::string s;
auto result = fmt::format_to_n(std::back_inserter(s), 1, "{}", 42);
EXPECT_EQ(2u, result.size);
char buffer[4] = {0};
fmt::format_to_n(buffer, 3, "{}", 12345);
std::wstring w;
auto wresult = fmt::format_to_n(std::back_inserter(w), 1, L"{}", 42);
EXPECT_EQ(2u, wresult.size);
wchar_t wbuffer[4] = {0};
fmt::format_to_n(wbuffer, 3, L"{}", 12345);
}
TEST(module_test, format_args) {
auto no_args = fmt::format_args();
EXPECT_FALSE(no_args.get(1));
fmt::basic_format_args args = fmt::make_format_args(42);
EXPECT_TRUE(args.max_size() > 0);
auto arg0 = args.get(0);
EXPECT_TRUE(arg0);
decltype(arg0) arg_none;
EXPECT_FALSE(arg_none);
EXPECT_TRUE(arg0.type() != arg_none.type());
}
TEST(module_test, wformat_args) {
auto no_args = fmt::wformat_args();
EXPECT_FALSE(no_args.get(1));
fmt::basic_format_args args = fmt::make_wformat_args(42);
EXPECT_TRUE(args.get(0));
}
TEST(module_test, checked_format_args) {
fmt::basic_format_args args = fmt::make_args_checked<int>("{}", 42);
EXPECT_TRUE(args.get(0));
fmt::basic_format_args wargs = fmt::make_args_checked<int>(L"{}", 42);
EXPECT_TRUE(wargs.get(0));
}
TEST(module_test, dynamic_format_args) {
fmt::dynamic_format_arg_store<fmt::format_context> dyn_store;
dyn_store.push_back(fmt::arg("a42", 42));
fmt::basic_format_args args = dyn_store;
EXPECT_FALSE(args.get(3));
EXPECT_TRUE(args.get(fmt::string_view("a42")));
fmt::dynamic_format_arg_store<fmt::wformat_context> wdyn_store;
wdyn_store.push_back(fmt::arg(L"a42", 42));
fmt::basic_format_args wargs = wdyn_store;
EXPECT_FALSE(wargs.get(3));
EXPECT_TRUE(wargs.get(fmt::wstring_view(L"a42")));
}
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)));
}
TEST(module_test, vformat_to) {
auto store = fmt::make_format_args(42);
std::string s;
fmt::vformat_to(std::back_inserter(s), "{}", store);
EXPECT_EQ("42", s);
char buffer[4] = {0};
fmt::vformat_to(buffer, "{:}", store);
EXPECT_EQ("42", std::string_view(buffer));
auto wstore = fmt::make_wformat_args(42);
std::wstring w;
fmt::vformat_to(std::back_inserter(w), L"{}", wstore);
EXPECT_EQ(L"42", w);
wchar_t wbuffer[4] = {0};
fmt::vformat_to(wbuffer, L"{:}", wstore);
EXPECT_EQ(L"42", std::wstring_view(wbuffer));
}
TEST(module_test, vformat_to_n) {
auto store = fmt::make_format_args(12345);
std::string s;
auto result = fmt::vformat_to_n(std::back_inserter(s), 1, "{}", store);
char buffer[4] = {0};
fmt::vformat_to_n(buffer, 3, "{:}", store);
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);
wchar_t wbuffer[4] = {0};
fmt::vformat_to_n(wbuffer, 3, fmt::to_string_view(L"{:}"), wstore);
}
std::string as_string(std::wstring_view text) {
return {reinterpret_cast<const char*>(text.data()),
text.size() * sizeof(text[0])};
}
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) {
EXPECT_EQ("42", fmt::format("{answer}", fmt::arg("answer", 42)));
EXPECT_EQ(L"42", fmt::format(L"{answer}", fmt::arg(L"answer", 42)));
}
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) {
auto store = fmt::make_format_args(4.2);
const auto classic = std::locale::classic();
EXPECT_EQ("4.2", fmt::format(classic, "{:L}", 4.2));
EXPECT_EQ("4.2", fmt::vformat(classic, "{:L}", store));
std::string s;
fmt::vformat_to(std::back_inserter(s), classic, "{:L}", store);
EXPECT_EQ("4.2", s);
EXPECT_EQ("4.2", fmt::format("{:L}", 4.2));
auto wstore = fmt::make_wformat_args(4.2);
EXPECT_EQ(L"4.2", fmt::format(classic, L"{:L}", 4.2));
EXPECT_EQ(L"4.2", fmt::vformat(classic, L"{:L}", wstore));
std::wstring w;
fmt::vformat_to(std::back_inserter(w), classic, L"{:L}", wstore);
EXPECT_EQ(L"4.2", w);
EXPECT_EQ(L"4.2", fmt::format(L"{:L}", 4.2));
}
TEST(module_test, string_view) {
fmt::string_view nsv("fmt");
EXPECT_EQ("fmt", nsv);
EXPECT_TRUE(fmt::string_view("fmt") == nsv);
fmt::wstring_view wsv(L"fmt");
EXPECT_EQ(L"fmt", wsv);
EXPECT_TRUE(fmt::wstring_view(L"fmt") == wsv);
}
TEST(module_test, memory_buffer) {
fmt::basic_memory_buffer<char, fmt::inline_buffer_size> buffer;
fmt::format_to(buffer, "{}", "42");
EXPECT_EQ("42", to_string(buffer));
fmt::memory_buffer nbuffer(std::move(buffer));
EXPECT_EQ("42", to_string(nbuffer));
buffer = std::move(nbuffer);
EXPECT_EQ("42", to_string(buffer));
nbuffer.clear();
EXPECT_EQ(0u, to_string(nbuffer).size());
fmt::wmemory_buffer wbuffer;
EXPECT_EQ(0u, to_string(wbuffer).size());
}
TEST(module_test, is_char) {
EXPECT_TRUE(fmt::is_char<char>());
EXPECT_TRUE(fmt::is_char<wchar_t>());
EXPECT_TRUE(fmt::is_char<char8_t>());
EXPECT_TRUE(fmt::is_char<char16_t>());
EXPECT_TRUE(fmt::is_char<char32_t>());
EXPECT_FALSE(fmt::is_char<signed char>());
}
TEST(module_test, ptr) {
uintptr_t answer = 42;
auto p = std::bit_cast<int*>(answer);
EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(p)));
std::unique_ptr<int> up(p);
EXPECT_EQ("0x2a", fmt::to_string(fmt::ptr(up)));
up.release();
auto sp = std::make_shared<int>(0);
p = sp.get();
EXPECT_EQ(fmt::to_string(fmt::ptr(p)), fmt::to_string(fmt::ptr(sp)));
}
TEST(module_test, errors) {
auto store = fmt::make_format_args(42);
EXPECT_THROW(throw fmt::format_error("oops"), std::exception);
EXPECT_THROW(throw fmt::vsystem_error(0, "{}", store), std::system_error);
EXPECT_THROW(throw fmt::system_error(0, "{}", 42), std::system_error);
fmt::memory_buffer buffer;
fmt::format_system_error(buffer, 0, "oops");
auto oops = to_string(buffer);
EXPECT_TRUE(oops.size() > 0);
EXPECT_WRITE(stderr, fmt::report_system_error(0, "oops"), oops + '\n');
#ifdef _WIN32
EXPECT_THROW(throw fmt::vwindows_error(0, "{}", store), std::system_error);
EXPECT_THROW(throw fmt::windows_error(0, "{}", 42), std::system_error);
output_redirect redirect(stderr);
fmt::report_windows_error(0, "oops");
EXPECT_TRUE(redirect.restore_and_read().size() > 0);
#endif
}
TEST(module_test, error_code) {
EXPECT_EQ("generic:42",
fmt::format("{0}", std::error_code(42, std::generic_category())));
EXPECT_EQ("system:42",
fmt::format("{0}", std::error_code(42, fmt::system_category())));
EXPECT_EQ(L"generic:42",
fmt::format(L"{0}", std::error_code(42, std::generic_category())));
}
TEST(module_test, format_int) {
fmt::format_int sanswer(42);
EXPECT_EQ("42", fmt::string_view(sanswer.data(), sanswer.size()));
fmt::format_int uanswer(42u);
EXPECT_EQ("42", fmt::string_view(uanswer.data(), uanswer.size()));
}
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, 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(", ");
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", ");
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)));
}
TEST(module_test, time) {
auto time_now = std::time(nullptr);
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);
}
TEST(module_test, time_point) {
auto now = std::chrono::system_clock::now();
std::string_view past("2021-05-20 10:30:15");
EXPECT_TRUE(past < fmt::format("{:%Y-%m-%d %H:%M:%S}", now));
std::wstring_view wpast(L"2021-05-20 10:30:15");
EXPECT_TRUE(wpast < fmt::format(L"{:%Y-%m-%d %H:%M:%S}", now));
}
TEST(module_test, time_duration) {
using us = std::chrono::duration<double, std::micro>;
EXPECT_EQ("42s", fmt::format("{}", std::chrono::seconds{42}));
EXPECT_EQ("4.2µs", fmt::format("{:3.1}", us{4.234}));
EXPECT_EQ("4.2µs", fmt::format(std::locale::classic(), "{:L}", us{4.2}));
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds{42}));
EXPECT_EQ(L"4.2µs", fmt::format(L"{:3.1}", us{4.234}));
EXPECT_EQ(L"4.2µs", fmt::format(std::locale::classic(), L"{:L}", us{4.2}));
}
TEST(module_test, weekday) {
EXPECT_EQ("Monday",
std::format(std::locale::classic(), "{:%A}", 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"%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) {
EXPECT_EQ("42", fmt::sprintf("%d", 42));
EXPECT_EQ(L"42", fmt::sprintf(L"%d", 42));
}
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) {
EXPECT_EQ("42", fmt::vsprintf("%d", fmt::make_printf_args(42)));
EXPECT_EQ(L"42", fmt::vsprintf(L"%d", fmt::make_wprintf_args(42)));
}
TEST(module_test, color) {
auto fg_check = fg(fmt::rgb(255, 200, 30));
auto bg_check = bg(fmt::color::dark_slate_gray) | fmt::emphasis::italic;
auto emphasis_check = fmt::emphasis::underline | fmt::emphasis::bold;
EXPECT_EQ("\x1B[30m42\x1B[0m",
fmt::format(fg(fmt::terminal_color::black), "{}", 42));
EXPECT_EQ(L"\x1B[30m42\x1B[0m",
fmt::format(fg(fmt::terminal_color::black), L"{}", 42));
}
TEST(module_test, cstring_view) {
auto s = "fmt";
EXPECT_EQ(s, fmt::cstring_view(s).c_str());
auto w = L"fmt";
EXPECT_EQ(w, fmt::wcstring_view(w).c_str());
}
TEST(module_test, buffered_file) {
EXPECT_TRUE(fmt::buffered_file{}.get() == nullptr);
}
TEST(module_test, output_file) {
fmt::ostream out = fmt::output_file("module-test", fmt::buffer_size = 1);
out.close();
}
struct custom_context {
using char_type = char;
using parse_context_type = fmt::format_parse_context;
};
TEST(module_test, custom_context) {
fmt::basic_format_arg<custom_context> custom_arg;
EXPECT_TRUE(!custom_arg);
}
struct disabled_formatter {};
TEST(module_test, has_formatter) {
EXPECT_FALSE(
(fmt::has_formatter<disabled_formatter, fmt::format_context>::value));
}
TEST(module_test, is_formattable) {
EXPECT_FALSE(fmt::is_formattable<disabled_formatter>::value);
}
TEST(module_test, compile_format_string) {
using namespace fmt::literals;
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"));
}

View File

@ -19,20 +19,21 @@
#endif #endif
using fmt::buffered_file; using fmt::buffered_file;
using fmt::error_code; using testing::HasSubstr;
using wstring_view = fmt::basic_string_view<wchar_t>;
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
TEST(UtilTest, UTF16ToUTF8) { TEST(util_test, utf16_to_utf8) {
std::string s = "ёжик"; auto s = std::string("ёжик");
fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A"); fmt::detail::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
EXPECT_EQ(s, u.str()); EXPECT_EQ(s, u.str());
EXPECT_EQ(s.size(), u.size()); EXPECT_EQ(s.size(), u.size());
} }
TEST(UtilTest, UTF16ToUTF8EmptyString) { TEST(util_test, utf16_to_utf8_empty_string) {
std::string s = ""; std::string s = "";
fmt::detail::utf16_to_utf8 u(L""); fmt::detail::utf16_to_utf8 u(L"");
EXPECT_EQ(s, u.str()); EXPECT_EQ(s, u.str());
@ -45,65 +46,73 @@ void check_utf_conversion_error(
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) { fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
fmt::memory_buffer out; fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message); fmt::detail::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
fmt::system_error error(0, ""); auto error = std::system_error(std::error_code());
try { try {
(Converter)(str); (Converter)(str);
} catch (const fmt::system_error& e) { } catch (const std::system_error& e) {
error = e; error = e;
} }
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code()); EXPECT_EQ(ERROR_INVALID_PARAMETER, error.code().value());
EXPECT_EQ(fmt::to_string(out), error.what()); EXPECT_THAT(error.what(), HasSubstr(fmt::to_string(out)));
} }
TEST(UtilTest, UTF16ToUTF8Error) { TEST(util_test, utf16_to_utf8_error) {
check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>( check_utf_conversion_error<fmt::detail::utf16_to_utf8, wchar_t>(
"cannot convert string from UTF-16 to UTF-8"); "cannot convert string from UTF-16 to UTF-8");
} }
TEST(UtilTest, UTF16ToUTF8Convert) { TEST(util_test, utf16_to_utf8_convert) {
fmt::detail::utf16_to_utf8 u; fmt::detail::utf16_to_utf8 u;
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1))); EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(wstring_view(0, 1)));
EXPECT_EQ(ERROR_INVALID_PARAMETER, EXPECT_EQ(ERROR_INVALID_PARAMETER,
u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u))); u.convert(wstring_view(L"foo", INT_MAX + 1u)));
} }
TEST(UtilTest, FormatWindowsError) { 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 = 0; LPWSTR message = 0;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | auto result = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
0, ERROR_FILE_EXISTS, 0, ERROR_FILE_EXISTS, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0); reinterpret_cast<LPWSTR>(&message), 0, 0);
fmt::detail::utf16_to_utf8 utf8_message(message); fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
LocalFree(message); LocalFree(message);
fmt::memory_buffer actual_message; fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test"); fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS, "test");
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()), EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
fmt::to_string(actual_message)); fmt::to_string(actual_message));
actual_message.resize(0); actual_message.resize(0);
auto max_size = fmt::detail::max_value<size_t>();
fmt::detail::format_windows_error(actual_message, ERROR_FILE_EXISTS,
fmt::string_view(0, max_size));
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
fmt::to_string(actual_message));
} }
TEST(UtilTest, FormatLongWindowsError) { TEST(os_test, format_long_windows_error) {
LPWSTR message = 0; LPWSTR message = 0;
// this error code is not available on all Windows platforms and // this error code is not available on all Windows platforms and
// Windows SDKs, so do not fail the test if the error string cannot // Windows SDKs, so do not fail the test if the error string cannot
// be retrieved. // be retrieved.
const int provisioning_not_allowed = int provisioning_not_allowed = 0x80284013L; // TBS_E_PROVISIONING_NOT_ALLOWED
0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/; auto result = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_IGNORE_INSERTS,
0, static_cast<DWORD>(provisioning_not_allowed), 0, static_cast<DWORD>(provisioning_not_allowed),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) { reinterpret_cast<LPWSTR>(&message), 0, 0);
if (result == 0) {
LocalFree(message);
return; return;
} }
fmt::detail::utf16_to_utf8 utf8_message(message); fmt::detail::utf16_to_utf8 utf8_message(wstring_view(message, result - 2));
LocalFree(message); LocalFree(message);
fmt::memory_buffer actual_message; fmt::memory_buffer actual_message;
fmt::detail::format_windows_error(actual_message, provisioning_not_allowed, fmt::detail::format_windows_error(actual_message, provisioning_not_allowed,
@ -112,20 +121,20 @@ TEST(UtilTest, FormatLongWindowsError) {
fmt::to_string(actual_message)); fmt::to_string(actual_message));
} }
TEST(UtilTest, WindowsError) { TEST(os_test, windows_error) {
fmt::system_error error(0, ""); auto error = std::system_error(std::error_code());
try { try {
throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error"); throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error");
} catch (const fmt::system_error& e) { } catch (const std::system_error& e) {
error = e; error = e;
} }
fmt::memory_buffer message; fmt::memory_buffer message;
fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error"); fmt::detail::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
EXPECT_EQ(to_string(message), error.what()); EXPECT_THAT(error.what(), HasSubstr(to_string(message)));
EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code()); EXPECT_EQ(ERROR_FILE_EXISTS, error.code().value());
} }
TEST(UtilTest, ReportWindowsError) { TEST(os_test, report_windows_error) {
fmt::memory_buffer out; fmt::memory_buffer out;
fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error"); fmt::detail::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
out.push_back('\n'); out.push_back('\n');
@ -140,30 +149,24 @@ TEST(UtilTest, ReportWindowsError) {
using fmt::file; using fmt::file;
// Checks if the file is open by reading one character from it. bool isclosed(int fd) {
static bool isopen(int fd) {
char buffer; char buffer;
return FMT_POSIX(read(fd, &buffer, 1)) == 1; auto result = std::streamsize();
}
static bool isclosed(int fd) {
char buffer;
std::streamsize result = 0;
SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1))); SUPPRESS_ASSERT(result = FMT_POSIX(read(fd, &buffer, 1)));
return result == -1 && errno == EBADF; return result == -1 && errno == EBADF;
} }
// Opens a file for reading. // Opens a file for reading.
static file open_file() { file open_file() {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); write_end.write(file_content, std::strlen(file_content));
write_end.close(); write_end.close();
return read_end; return read_end;
} }
// Attempts to write a string to a file. // Attempts to write a string to a file.
static void write(file& f, fmt::string_view s) { void write(file& f, fmt::string_view s) {
size_t num_chars_left = s.size(); size_t num_chars_left = s.size();
const char* ptr = s.data(); const char* ptr = s.data();
do { do {
@ -175,12 +178,12 @@ static void write(file& f, fmt::string_view s) {
} while (num_chars_left != 0); } while (num_chars_left != 0);
} }
TEST(BufferedFileTest, DefaultCtor) { TEST(buffered_file_test, default_ctor) {
buffered_file f; auto f = buffered_file();
EXPECT_TRUE(f.get() == nullptr); EXPECT_TRUE(f.get() == nullptr);
} }
TEST(BufferedFileTest, MoveCtor) { TEST(buffered_file_test, move_ctor) {
buffered_file bf = open_buffered_file(); buffered_file bf = open_buffered_file();
FILE* fp = bf.get(); FILE* fp = bf.get();
EXPECT_TRUE(fp != nullptr); EXPECT_TRUE(fp != nullptr);
@ -189,7 +192,7 @@ TEST(BufferedFileTest, MoveCtor) {
EXPECT_TRUE(bf.get() == nullptr); EXPECT_TRUE(bf.get() == nullptr);
} }
TEST(BufferedFileTest, MoveAssignment) { TEST(buffered_file_test, move_assignment) {
buffered_file bf = open_buffered_file(); buffered_file bf = open_buffered_file();
FILE* fp = bf.get(); FILE* fp = bf.get();
EXPECT_TRUE(fp != nullptr); EXPECT_TRUE(fp != nullptr);
@ -199,7 +202,7 @@ TEST(BufferedFileTest, MoveAssignment) {
EXPECT_TRUE(bf.get() == nullptr); EXPECT_TRUE(bf.get() == nullptr);
} }
TEST(BufferedFileTest, MoveAssignmentClosesFile) { TEST(buffered_file_test, move_assignment_closes_file) {
buffered_file bf = open_buffered_file(); buffered_file bf = open_buffered_file();
buffered_file bf2 = open_buffered_file(); buffered_file bf2 = open_buffered_file();
int old_fd = bf2.fileno(); int old_fd = bf2.fileno();
@ -207,27 +210,27 @@ TEST(BufferedFileTest, MoveAssignmentClosesFile) {
EXPECT_TRUE(isclosed(old_fd)); EXPECT_TRUE(isclosed(old_fd));
} }
TEST(BufferedFileTest, MoveFromTemporaryInCtor) { TEST(buffered_file_test, move_from_temporary_in_ctor) {
FILE* fp = nullptr; FILE* fp = nullptr;
buffered_file f(open_buffered_file(&fp)); buffered_file f = open_buffered_file(&fp);
EXPECT_EQ(fp, f.get()); EXPECT_EQ(fp, f.get());
} }
TEST(BufferedFileTest, MoveFromTemporaryInAssignment) { TEST(buffered_file_test, move_from_temporary_in_assignment) {
FILE* fp = nullptr; FILE* fp = nullptr;
buffered_file f; auto f = buffered_file();
f = open_buffered_file(&fp); f = open_buffered_file(&fp);
EXPECT_EQ(fp, f.get()); EXPECT_EQ(fp, f.get());
} }
TEST(BufferedFileTest, MoveFromTemporaryInAssignmentClosesFile) { TEST(buffered_file_test, move_from_temporary_in_assignment_closes_file) {
buffered_file f = open_buffered_file(); buffered_file f = open_buffered_file();
int old_fd = f.fileno(); int old_fd = f.fileno();
f = open_buffered_file(); f = open_buffered_file();
EXPECT_TRUE(isclosed(old_fd)); EXPECT_TRUE(isclosed(old_fd));
} }
TEST(BufferedFileTest, CloseFileInDtor) { TEST(buffered_file_test, close_file_in_dtor) {
int fd = 0; int fd = 0;
{ {
buffered_file f = open_buffered_file(); buffered_file f = open_buffered_file();
@ -236,8 +239,9 @@ TEST(BufferedFileTest, CloseFileInDtor) {
EXPECT_TRUE(isclosed(fd)); EXPECT_TRUE(isclosed(fd));
} }
TEST(BufferedFileTest, CloseErrorInDtor) { TEST(buffered_file_test, close_error_in_dtor) {
std::unique_ptr<buffered_file> f(new buffered_file(open_buffered_file())); auto f =
std::unique_ptr<buffered_file>(new buffered_file(open_buffered_file()));
EXPECT_WRITE( EXPECT_WRITE(
stderr, stderr,
{ {
@ -248,10 +252,10 @@ TEST(BufferedFileTest, CloseErrorInDtor) {
FMT_POSIX(close(f->fileno())); FMT_POSIX(close(f->fileno()));
SUPPRESS_ASSERT(f.reset(nullptr)); SUPPRESS_ASSERT(f.reset(nullptr));
}, },
format_system_error(EBADF, "cannot close file") + "\n"); system_error_message(EBADF, "cannot close file") + "\n");
} }
TEST(BufferedFileTest, Close) { TEST(buffered_file_test, close) {
buffered_file f = open_buffered_file(); buffered_file f = open_buffered_file();
int fd = f.fileno(); int fd = f.fileno();
f.close(); f.close();
@ -259,73 +263,101 @@ TEST(BufferedFileTest, Close) {
EXPECT_TRUE(isclosed(fd)); EXPECT_TRUE(isclosed(fd));
} }
TEST(BufferedFileTest, CloseError) { TEST(buffered_file_test, close_error) {
buffered_file f = open_buffered_file(); buffered_file f = open_buffered_file();
FMT_POSIX(close(f.fileno())); FMT_POSIX(close(f.fileno()));
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
EXPECT_TRUE(f.get() == nullptr); EXPECT_TRUE(f.get() == nullptr);
} }
TEST(BufferedFileTest, Fileno) { TEST(buffered_file_test, fileno) {
buffered_file f; auto f = open_buffered_file();
# ifndef __COVERITY__
// fileno on a null FILE pointer either crashes or returns an error.
// Disable Coverity because this is intentional.
EXPECT_DEATH_IF_SUPPORTED(
{
try {
f.fileno();
} catch (const fmt::system_error&) {
std::exit(1);
}
},
"");
# endif
f = open_buffered_file();
EXPECT_TRUE(f.fileno() != -1); EXPECT_TRUE(f.fileno() != -1);
file copy = file::dup(f.fileno()); file copy = file::dup(f.fileno());
EXPECT_READ(copy, FILE_CONTENT); EXPECT_READ(copy, file_content);
} }
TEST(DirectBufferedFileTest, Print) { TEST(ostream_test, move) {
fmt::direct_buffered_file out( fmt::ostream out = fmt::output_file("test-file");
"test-file", fmt::file::WRONLY | fmt::file::CREATE); fmt::ostream moved(std::move(out));
fmt::print(out, "The answer is {}.\n", 42); moved.print("hello");
}
TEST(ostream_test, move_while_holding_data) {
{
fmt::ostream out = fmt::output_file("test-file");
out.print("Hello, ");
fmt::ostream moved(std::move(out));
moved.print("world!\n");
}
{
file in("test-file", file::RDONLY);
EXPECT_READ(in, "Hello, world!\n");
}
}
TEST(ostream_test, print) {
fmt::ostream out = fmt::output_file("test-file");
out.print("The answer is {}.\n",
fmt::join(std::initializer_list<int>{42}, ", "));
out.close(); out.close();
file in("test-file", file::RDONLY); file in("test-file", file::RDONLY);
EXPECT_READ(in, "The answer is 42.\n"); EXPECT_READ(in, "The answer is 42.\n");
} }
TEST(DirectBufferedFileTest, BufferBoundary) { TEST(ostream_test, buffer_boundary) {
auto str = std::string(4096, 'x'); auto str = std::string(4096, 'x');
fmt::direct_buffered_file out( fmt::ostream out = fmt::output_file("test-file");
"test-file", fmt::file::WRONLY | fmt::file::CREATE); out.print("{}", str);
fmt::print(out, "{}", str); out.print("{}", str);
fmt::print(out, "{}", str);
out.close(); out.close();
file in("test-file", file::RDONLY); file in("test-file", file::RDONLY);
EXPECT_READ(in, str + str); EXPECT_READ(in, str + str);
} }
TEST(FileTest, DefaultCtor) { TEST(ostream_test, buffer_size) {
fmt::ostream out = fmt::output_file("test-file", fmt::buffer_size = 1);
out.print("{}", "foo");
out.close();
file in("test-file", file::RDONLY);
EXPECT_READ(in, "foo");
}
TEST(ostream_test, truncate) {
{
fmt::ostream out = fmt::output_file("test-file");
out.print("0123456789");
}
{
fmt::ostream out = fmt::output_file("test-file");
out.print("foo");
}
file in("test-file", file::RDONLY);
EXPECT_EQ("foo", read(in, 4));
}
TEST(file_test, default_ctor) {
file f; file f;
EXPECT_EQ(-1, f.descriptor()); EXPECT_EQ(-1, f.descriptor());
} }
TEST(FileTest, OpenBufferedFileInCtor) { TEST(file_test, open_buffered_file_in_ctor) {
FILE* fp = safe_fopen("test-file", "w"); FILE* fp = safe_fopen("test-file", "w");
std::fputs(FILE_CONTENT, fp); std::fputs(file_content, fp);
std::fclose(fp); std::fclose(fp);
file f("test-file", file::RDONLY); file f("test-file", file::RDONLY);
ASSERT_TRUE(isopen(f.descriptor())); // Check if the file is open by reading one character from it.
char buffer;
bool isopen = FMT_POSIX(read(f.descriptor(), &buffer, 1)) == 1;
ASSERT_TRUE(isopen);
} }
TEST(FileTest, OpenBufferedFileError) { TEST(file_test, open_buffered_file_error) {
EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT, EXPECT_SYSTEM_ERROR(file("nonexistent", file::RDONLY), ENOENT,
"cannot open file nonexistent"); "cannot open file nonexistent");
} }
TEST(FileTest, MoveCtor) { TEST(file_test, move_ctor) {
file f = open_file(); file f = open_file();
int fd = f.descriptor(); int fd = f.descriptor();
EXPECT_NE(-1, fd); EXPECT_NE(-1, fd);
@ -334,7 +366,7 @@ TEST(FileTest, MoveCtor) {
EXPECT_EQ(-1, f.descriptor()); EXPECT_EQ(-1, f.descriptor());
} }
TEST(FileTest, MoveAssignment) { TEST(file_test, move_assignment) {
file f = open_file(); file f = open_file();
int fd = f.descriptor(); int fd = f.descriptor();
EXPECT_NE(-1, fd); EXPECT_NE(-1, fd);
@ -344,7 +376,7 @@ TEST(FileTest, MoveAssignment) {
EXPECT_EQ(-1, f.descriptor()); EXPECT_EQ(-1, f.descriptor());
} }
TEST(FileTest, MoveAssignmentClosesFile) { TEST(file_test, move_assignment_closes_file) {
file f = open_file(); file f = open_file();
file f2 = open_file(); file f2 = open_file();
int old_fd = f2.descriptor(); int old_fd = f2.descriptor();
@ -352,34 +384,34 @@ TEST(FileTest, MoveAssignmentClosesFile) {
EXPECT_TRUE(isclosed(old_fd)); EXPECT_TRUE(isclosed(old_fd));
} }
static file OpenBufferedFile(int& fd) { file open_buffered_file(int& fd) {
file f = open_file(); file f = open_file();
fd = f.descriptor(); fd = f.descriptor();
return f; return f;
} }
TEST(FileTest, MoveFromTemporaryInCtor) { TEST(file_test, move_from_temporary_in_ctor) {
int fd = 0xdead; int fd = 0xdead;
file f(OpenBufferedFile(fd)); file f(open_buffered_file(fd));
EXPECT_EQ(fd, f.descriptor()); EXPECT_EQ(fd, f.descriptor());
} }
TEST(FileTest, MoveFromTemporaryInAssignment) { TEST(file_test, move_from_temporary_in_assignment) {
int fd = 0xdead; int fd = 0xdead;
file f; file f;
f = OpenBufferedFile(fd); f = open_buffered_file(fd);
EXPECT_EQ(fd, f.descriptor()); EXPECT_EQ(fd, f.descriptor());
} }
TEST(FileTest, MoveFromTemporaryInAssignmentClosesFile) { TEST(file_test, move_from_temporary_in_assignment_closes_file) {
int fd = 0xdead; int fd = 0xdead;
file f = open_file(); file f = open_file();
int old_fd = f.descriptor(); int old_fd = f.descriptor();
f = OpenBufferedFile(fd); f = open_buffered_file(fd);
EXPECT_TRUE(isclosed(old_fd)); EXPECT_TRUE(isclosed(old_fd));
} }
TEST(FileTest, CloseFileInDtor) { TEST(file_test, close_file_in_dtor) {
int fd = 0; int fd = 0;
{ {
file f = open_file(); file f = open_file();
@ -388,7 +420,7 @@ TEST(FileTest, CloseFileInDtor) {
EXPECT_TRUE(isclosed(fd)); EXPECT_TRUE(isclosed(fd));
} }
TEST(FileTest, CloseErrorInDtor) { TEST(file_test, close_error_in_dtor) {
std::unique_ptr<file> f(new file(open_file())); std::unique_ptr<file> f(new file(open_file()));
EXPECT_WRITE( EXPECT_WRITE(
stderr, stderr,
@ -400,10 +432,10 @@ TEST(FileTest, CloseErrorInDtor) {
FMT_POSIX(close(f->descriptor())); FMT_POSIX(close(f->descriptor()));
SUPPRESS_ASSERT(f.reset(nullptr)); SUPPRESS_ASSERT(f.reset(nullptr));
}, },
format_system_error(EBADF, "cannot close file") + "\n"); system_error_message(EBADF, "cannot close file") + "\n");
} }
TEST(FileTest, Close) { TEST(file_test, close) {
file f = open_file(); file f = open_file();
int fd = f.descriptor(); int fd = f.descriptor();
f.close(); f.close();
@ -411,19 +443,19 @@ TEST(FileTest, Close) {
EXPECT_TRUE(isclosed(fd)); EXPECT_TRUE(isclosed(fd));
} }
TEST(FileTest, CloseError) { TEST(file_test, close_error) {
file f = open_file(); file f = open_file();
FMT_POSIX(close(f.descriptor())); FMT_POSIX(close(f.descriptor()));
EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file"); EXPECT_SYSTEM_ERROR_NOASSERT(f.close(), EBADF, "cannot close file");
EXPECT_EQ(-1, f.descriptor()); EXPECT_EQ(-1, f.descriptor());
} }
TEST(FileTest, Read) { TEST(file_test, read) {
file f = open_file(); file f = open_file();
EXPECT_READ(f, FILE_CONTENT); EXPECT_READ(f, file_content);
} }
TEST(FileTest, ReadError) { TEST(file_test, read_error) {
file f("test-file", file::WRONLY); file f("test-file", file::WRONLY);
char buf; char buf;
// We intentionally read from a file opened in the write-only mode to // We intentionally read from a file opened in the write-only mode to
@ -431,7 +463,7 @@ TEST(FileTest, ReadError) {
EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file"); EXPECT_SYSTEM_ERROR(f.read(&buf, 1), EBADF, "cannot read from file");
} }
TEST(FileTest, Write) { TEST(file_test, write) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
write(write_end, "test"); write(write_end, "test");
@ -439,61 +471,61 @@ TEST(FileTest, Write) {
EXPECT_READ(read_end, "test"); EXPECT_READ(read_end, "test");
} }
TEST(FileTest, WriteError) { TEST(file_test, write_error) {
file f("test-file", file::RDONLY); file f("test-file", file::RDONLY);
// We intentionally write to a file opened in the read-only mode to // We intentionally write to a file opened in the read-only mode to
// cause error. // cause error.
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file"); EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
} }
TEST(FileTest, Dup) { TEST(file_test, dup) {
file f = open_file(); file f = open_file();
file copy = file::dup(f.descriptor()); file copy = file::dup(f.descriptor());
EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_NE(f.descriptor(), copy.descriptor());
EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT))); EXPECT_EQ(file_content, read(copy, std::strlen(file_content)));
} }
# ifndef __COVERITY__ # ifndef __COVERITY__
TEST(FileTest, DupError) { TEST(file_test, dup_error) {
int value = -1; int value = -1;
EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF, EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF,
"cannot duplicate file descriptor -1"); "cannot duplicate file descriptor -1");
} }
# endif # endif
TEST(FileTest, Dup2) { TEST(file_test, dup2) {
file f = open_file(); file f = open_file();
file copy = open_file(); file copy = open_file();
f.dup2(copy.descriptor()); f.dup2(copy.descriptor());
EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_NE(f.descriptor(), copy.descriptor());
EXPECT_READ(copy, FILE_CONTENT); EXPECT_READ(copy, file_content);
} }
TEST(FileTest, Dup2Error) { TEST(file_test, dup2_error) {
file f = open_file(); file f = open_file();
EXPECT_SYSTEM_ERROR_NOASSERT( EXPECT_SYSTEM_ERROR_NOASSERT(
f.dup2(-1), EBADF, f.dup2(-1), EBADF,
fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor())); fmt::format("cannot duplicate file descriptor {} to -1", f.descriptor()));
} }
TEST(FileTest, Dup2NoExcept) { TEST(file_test, dup2_noexcept) {
file f = open_file(); file f = open_file();
file copy = open_file(); file copy = open_file();
error_code ec; std::error_code ec;
f.dup2(copy.descriptor(), ec); f.dup2(copy.descriptor(), ec);
EXPECT_EQ(ec.get(), 0); EXPECT_EQ(ec.value(), 0);
EXPECT_NE(f.descriptor(), copy.descriptor()); EXPECT_NE(f.descriptor(), copy.descriptor());
EXPECT_READ(copy, FILE_CONTENT); EXPECT_READ(copy, file_content);
} }
TEST(FileTest, Dup2NoExceptError) { TEST(file_test, dup2_noexcept_error) {
file f = open_file(); file f = open_file();
error_code ec; std::error_code ec;
SUPPRESS_ASSERT(f.dup2(-1, ec)); SUPPRESS_ASSERT(f.dup2(-1, ec));
EXPECT_EQ(EBADF, ec.get()); EXPECT_EQ(EBADF, ec.value());
} }
TEST(FileTest, Pipe) { TEST(file_test, pipe) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
EXPECT_NE(-1, read_end.descriptor()); EXPECT_NE(-1, read_end.descriptor());
@ -502,7 +534,7 @@ TEST(FileTest, Pipe) {
EXPECT_READ(read_end, "test"); EXPECT_READ(read_end, "test");
} }
TEST(FileTest, Fdopen) { TEST(file_test, fdopen) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
int read_fd = read_end.descriptor(); int read_fd = read_end.descriptor();
@ -510,7 +542,7 @@ TEST(FileTest, Fdopen) {
} }
# ifdef FMT_LOCALE # ifdef FMT_LOCALE
TEST(LocaleTest, Strtod) { TEST(locale_test, strtod) {
fmt::locale loc; fmt::locale loc;
const char *start = "4.2", *ptr = start; const char *start = "4.2", *ptr = start;
EXPECT_EQ(4.2, loc.strtod(ptr)); EXPECT_EQ(4.2, loc.strtod(ptr));

View File

@ -5,17 +5,17 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#define FMT_STRING_ALIAS 1
#include "fmt/format.h" #include "fmt/format.h"
using fmt::runtime;
struct test {}; struct test {};
// Test that there is no issues with specializations when fmt/ostream.h is // Test that there is no issues with specializations when fmt/ostream.h is
// included after fmt/format.h. // included after fmt/format.h.
namespace fmt { namespace fmt {
template <> struct formatter<test> : formatter<int> { template <> struct formatter<test> : formatter<int> {
template <typename FormatContext> auto format(const test&, format_context& ctx) -> decltype(ctx.out()) {
typename FormatContext::iterator format(const test&, FormatContext& ctx) {
return formatter<int>::format(42, ctx); return formatter<int>::format(42, ctx);
} }
}; };
@ -24,19 +24,17 @@ template <> struct formatter<test> : formatter<int> {
#include <sstream> #include <sstream>
#include "fmt/ostream.h" #include "fmt/ostream.h"
#include "gmock.h" #include "fmt/ranges.h"
#include "gmock/gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
using fmt::format; std::ostream& operator<<(std::ostream& os, const date& d) {
using fmt::format_error;
static std::ostream& operator<<(std::ostream& os, const Date& d) {
os << d.year() << '-' << d.month() << '-' << d.day(); os << d.year() << '-' << d.month() << '-' << d.day();
return os; return os;
} }
static std::wostream& operator<<(std::wostream& os, const Date& d) { std::wostream& operator<<(std::wostream& os, const date& d) {
os << d.year() << L'-' << d.month() << L'-' << d.day(); os << d.year() << L'-' << d.month() << L'-' << d.day();
return os; return os;
} }
@ -44,99 +42,63 @@ static std::wostream& operator<<(std::wostream& os, const Date& d) {
// Make sure that overloaded comma operators do no harm to is_streamable. // Make sure that overloaded comma operators do no harm to is_streamable.
struct type_with_comma_op {}; struct type_with_comma_op {};
template <typename T> void operator,(type_with_comma_op, const T&); template <typename T> void operator,(type_with_comma_op, const T&);
template <typename T> type_with_comma_op operator<<(T&, const Date&); template <typename T> type_with_comma_op operator<<(T&, const date&);
enum streamable_enum {}; enum streamable_enum {};
static std::ostream& operator<<(std::ostream& os, streamable_enum) {
return os << "streamable_enum";
}
static std::wostream& operator<<(std::wostream& os, streamable_enum) { std::ostream& operator<<(std::ostream& os, streamable_enum) {
return os << L"streamable_enum"; return os << "streamable_enum";
} }
enum unstreamable_enum {}; enum unstreamable_enum {};
TEST(OStreamTest, Enum) { TEST(ostream_test, enum) {
EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum())); EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
EXPECT_EQ("0", fmt::format("{}", unstreamable_enum())); EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
} }
struct test_arg_formatter TEST(ostream_test, format) {
: fmt::detail::arg_formatter<fmt::format_context::iterator, char> { EXPECT_EQ("a string", fmt::format("{0}", test_string("a string")));
fmt::format_parse_context parse_ctx; EXPECT_EQ("The date is 2012-12-9",
test_arg_formatter(fmt::format_context& ctx, fmt::format_specs& s) fmt::format("The date is {0}", date(2012, 12, 9)));
: fmt::detail::arg_formatter<fmt::format_context::iterator, char>(
ctx, &parse_ctx, &s),
parse_ctx("") {}
};
TEST(OStreamTest, CustomArg) {
fmt::memory_buffer buffer;
fmt::detail::buffer<char>& base = buffer;
fmt::format_context ctx(std::back_inserter(base), fmt::format_args());
fmt::format_specs spec;
test_arg_formatter af(ctx, spec);
fmt::visit_format_arg(
af, fmt::detail::make_arg<fmt::format_context>(streamable_enum()));
EXPECT_EQ("streamable_enum", std::string(buffer.data(), buffer.size()));
} }
TEST(OStreamTest, Format) { TEST(ostream_test, format_specs) {
EXPECT_EQ("a string", format("{0}", TestString("a string"))); using fmt::format_error;
std::string s = format("The date is {0}", Date(2012, 12, 9)); EXPECT_EQ("def ", fmt::format("{0:<5}", test_string("def")));
EXPECT_EQ("The date is 2012-12-9", s); EXPECT_EQ(" def", fmt::format("{0:>5}", test_string("def")));
Date date(2012, 12, 9); EXPECT_EQ(" def ", fmt::format("{0:^5}", test_string("def")));
EXPECT_EQ(L"The date is 2012-12-9", EXPECT_EQ("def**", fmt::format("{0:*<5}", test_string("def")));
format(L"The date is {0}", Date(2012, 12, 9))); EXPECT_THROW_MSG(fmt::format(runtime("{0:+}"), test_string()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:-}"), test_string()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0: }"), test_string()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:#}"), test_string()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(fmt::format(runtime("{0:05}"), test_string()), format_error,
"format specifier requires numeric argument");
EXPECT_EQ("test ", fmt::format("{0:13}", test_string("test")));
EXPECT_EQ("test ", fmt::format("{0:{1}}", test_string("test"), 13));
EXPECT_EQ("te", fmt::format("{0:.2}", test_string("test")));
EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2));
} }
TEST(OStreamTest, FormatSpecs) { struct empty_test {};
EXPECT_EQ("def ", format("{0:<5}", TestString("def"))); std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; }
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
#if FMT_DEPRECATED_NUMERIC_ALIGN TEST(ostream_test, empty_custom_output) {
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error, EXPECT_EQ("", fmt::format("{}", empty_test()));
"format specifier requires numeric argument");
#endif
EXPECT_EQ(" def ", format("{0:^5}", TestString("def")));
EXPECT_EQ("def**", format("{0:*<5}", TestString("def")));
EXPECT_THROW_MSG(format("{0:+}", TestString()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(format("{0:-}", TestString()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(format("{0: }", TestString()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(format("{0:#}", TestString()), format_error,
"format specifier requires numeric argument");
EXPECT_THROW_MSG(format("{0:05}", TestString()), format_error,
"format specifier requires numeric argument");
EXPECT_EQ("test ", format("{0:13}", TestString("test")));
EXPECT_EQ("test ", format("{0:{1}}", TestString("test"), 13));
EXPECT_EQ("te", format("{0:.2}", TestString("test")));
EXPECT_EQ("te", format("{0:.{1}}", TestString("test"), 2));
} }
struct EmptyTest {}; TEST(ostream_test, print) {
static std::ostream& operator<<(std::ostream& os, EmptyTest) {
return os << "";
}
TEST(OStreamTest, EmptyCustomOutput) {
EXPECT_EQ("", fmt::format("{}", EmptyTest()));
}
TEST(OStreamTest, Print) {
std::ostringstream os; std::ostringstream os;
fmt::print(os, "Don't {}!", "panic"); fmt::print(os, "Don't {}!", "panic");
EXPECT_EQ("Don't panic!", os.str()); EXPECT_EQ("Don't panic!", os.str());
std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"panic");
EXPECT_EQ(L"Don't panic!", wos.str());
} }
TEST(OStreamTest, WriteToOStream) { TEST(ostream_test, write_to_ostream) {
std::ostringstream os; std::ostringstream os;
fmt::memory_buffer buffer; fmt::memory_buffer buffer;
const char* foo = "foo"; const char* foo = "foo";
@ -145,13 +107,14 @@ TEST(OStreamTest, WriteToOStream) {
EXPECT_EQ("foo", os.str()); EXPECT_EQ("foo", os.str());
} }
TEST(OStreamTest, WriteToOStreamMaxSize) { TEST(ostream_test, write_to_ostream_max_size) {
size_t max_size = fmt::detail::max_value<size_t>(); auto max_size = fmt::detail::max_value<size_t>();
std::streamsize max_streamsize = fmt::detail::max_value<std::streamsize>(); auto max_streamsize = fmt::detail::max_value<std::streamsize>();
if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return; if (max_size <= fmt::detail::to_unsigned(max_streamsize)) return;
struct test_buffer : fmt::detail::buffer<char> { struct test_buffer final : fmt::detail::buffer<char> {
explicit test_buffer(size_t size) { resize(size); } explicit test_buffer(size_t size)
: fmt::detail::buffer<char>(nullptr, size, size) {}
void grow(size_t) {} void grow(size_t) {}
} buffer(max_size); } buffer(max_size);
@ -164,12 +127,13 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
} streambuf; } streambuf;
struct test_ostream : std::ostream { struct test_ostream : std::ostream {
explicit test_ostream(mock_streambuf& buffer) : std::ostream(&buffer) {} explicit test_ostream(mock_streambuf& output_buffer)
: std::ostream(&output_buffer) {}
} os(streambuf); } os(streambuf);
testing::InSequence sequence; testing::InSequence sequence;
const char* data = nullptr; const char* data = nullptr;
typedef std::make_unsigned<std::streamsize>::type ustreamsize; using ustreamsize = std::make_unsigned<std::streamsize>::type;
ustreamsize size = max_size; ustreamsize size = max_size;
do { do {
auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize)); auto n = std::min(size, fmt::detail::to_unsigned(max_streamsize));
@ -181,68 +145,62 @@ TEST(OStreamTest, WriteToOStreamMaxSize) {
fmt::detail::write_buffer(os, buffer); fmt::detail::write_buffer(os, buffer);
} }
TEST(OStreamTest, Join) { TEST(ostream_test, join) {
int v[3] = {1, 2, 3}; int v[3] = {1, 2, 3};
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", "))); EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join(v, v + 3, ", ")));
} }
TEST(ostream_test, join_fallback_formatter) {
auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
EXPECT_EQ("foo, bar", fmt::format("{}", fmt::join(strs, ", ")));
}
#if FMT_USE_CONSTEXPR #if FMT_USE_CONSTEXPR
TEST(OStreamTest, ConstexprString) { TEST(ostream_test, constexpr_string) {
EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42"))); EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
EXPECT_EQ("a string", format(FMT_STRING("{0}"), TestString("a string"))); EXPECT_EQ("a string", format(FMT_STRING("{0}"), test_string("a string")));
} }
#endif #endif
namespace fmt_test { namespace fmt_test {
struct ABC {}; struct abc {};
template <typename Output> Output& operator<<(Output& out, ABC) { template <typename Output> Output& operator<<(Output& out, abc) {
out << "ABC"; return out << "abc";
return out;
} }
} // namespace fmt_test } // namespace fmt_test
template <typename T> struct TestTemplate {}; template <typename T> struct test_template {};
template <typename T> template <typename T>
std::ostream& operator<<(std::ostream& os, TestTemplate<T>) { std::ostream& operator<<(std::ostream& os, test_template<T>) {
return os << 1; return os << 1;
} }
namespace fmt { namespace fmt {
template <typename T> struct formatter<TestTemplate<T>> : formatter<int> { template <typename T> struct formatter<test_template<T>> : formatter<int> {
template <typename FormatContext> auto format(test_template<T>, format_context& ctx) -> decltype(ctx.out()) {
typename FormatContext::iterator format(TestTemplate<T>, FormatContext& ctx) {
return formatter<int>::format(2, ctx); return formatter<int>::format(2, ctx);
} }
}; };
} // namespace fmt } // namespace fmt
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 407 TEST(ostream_test, template) {
TEST(OStreamTest, Template) { EXPECT_EQ("2", fmt::format("{}", test_template<int>()));
EXPECT_EQ("2", fmt::format("{}", TestTemplate<int>()));
} }
TEST(FormatTest, FormatToN) { TEST(ostream_test, format_to_n) {
char buffer[4]; char buffer[4];
buffer[3] = 'x'; buffer[3] = 'x';
auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::ABC()); auto result = fmt::format_to_n(buffer, 3, "{}", fmt_test::abc());
EXPECT_EQ(3u, result.size); EXPECT_EQ(3u, result.size);
EXPECT_EQ(buffer + 3, result.out); EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ("ABCx", fmt::string_view(buffer, 4)); EXPECT_EQ("abcx", fmt::string_view(buffer, 4));
result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::ABC()); result = fmt::format_to_n(buffer, 3, "x{}y", fmt_test::abc());
EXPECT_EQ(5u, result.size); EXPECT_EQ(5u, result.size);
EXPECT_EQ(buffer + 3, result.out); EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ("xABx", fmt::string_view(buffer, 4)); EXPECT_EQ("xabx", fmt::string_view(buffer, 4));
} }
#endif
#if FMT_USE_USER_DEFINED_LITERALS
TEST(FormatTest, UDL) {
using namespace fmt::literals;
EXPECT_EQ("{}"_format("test"), "test");
}
#endif
template <typename T> struct convertible { template <typename T> struct convertible {
T value; T value;
@ -250,9 +208,8 @@ template <typename T> struct convertible {
operator T() const { return value; } operator T() const { return value; }
}; };
TEST(OStreamTest, DisableBuiltinOStreamOperators) { TEST(ostream_test, disable_builtin_ostream_operators) {
EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42))); EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo"))); EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
} }
@ -270,7 +227,7 @@ std::ostream& operator<<(std::ostream& os,
return os << "bar"; return os << "bar";
} }
TEST(OStreamTest, FormatExplicitlyConvertibleToStringLike) { TEST(ostream_test, format_explicitly_convertible_to_string_like) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
} }
@ -286,12 +243,23 @@ std::ostream& operator<<(std::ostream& os,
return os << "bar"; return os << "bar";
} }
TEST(OStreamTest, FormatExplicitlyConvertibleToStdStringView) { TEST(ostream_test, format_explicitly_convertible_to_std_string_view) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like())); EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
} }
#endif // FMT_USE_STRING_VIEW #endif // FMT_USE_STRING_VIEW
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) {
EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool()));
}
struct copyfmt_test {}; struct copyfmt_test {};
std::ostream& operator<<(std::ostream& os, copyfmt_test) { std::ostream& operator<<(std::ostream& os, copyfmt_test) {
@ -300,10 +268,15 @@ std::ostream& operator<<(std::ostream& os, copyfmt_test) {
return os << "foo"; return os << "foo";
} }
TEST(OStreamTest, CopyFmt) { TEST(ostream_test, copyfmt) {
EXPECT_EQ("foo", fmt::format("{}", copyfmt_test())); EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
} }
TEST(OStreamTest, CompileTimeString) { TEST(ostream_test, to_string) {
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); EXPECT_EQ("abc", fmt::to_string(fmt_test::abc()));
}
TEST(ostream_test, range) {
auto strs = std::vector<test_string>{test_string("foo"), test_string("bar")};
EXPECT_EQ("[foo, bar]", fmt::format("{}", strs));
} }

View File

@ -23,20 +23,23 @@
#ifdef _WIN32 #ifdef _WIN32
# include <io.h> # include <io.h>
# undef max # undef max
# undef ERROR
#endif #endif
#include "gmock.h" #include "gmock/gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
using fmt::buffered_file; using fmt::buffered_file;
using fmt::error_code;
using testing::_; using testing::_;
using testing::Return; using testing::Return;
using testing::StrEq; using testing::StrEq;
template <typename Mock> struct scoped_mock : testing::StrictMock<Mock> {
scoped_mock() { Mock::instance = this; }
~scoped_mock() { Mock::instance = nullptr; }
};
namespace { namespace {
int open_count; int open_count;
int close_count; int close_count;
@ -53,7 +56,7 @@ size_t read_nbyte;
size_t write_nbyte; size_t write_nbyte;
bool sysconf_error; bool sysconf_error;
enum { NONE, MAX_SIZE, ERROR } fstat_sim; enum { none, max_size, error } fstat_sim;
} // namespace } // namespace
#define EMULATE_EINTR(func, error_result) \ #define EMULATE_EINTR(func, error_result) \
@ -91,7 +94,7 @@ static off_t max_file_size() { return std::numeric_limits<off_t>::max(); }
int test::fstat(int fd, struct stat* buf) { int test::fstat(int fd, struct stat* buf) {
int result = ::fstat(fd, buf); int result = ::fstat(fd, buf);
if (fstat_sim == MAX_SIZE) buf->st_size = max_file_size(); if (fstat_sim == max_size) buf->st_size = max_file_size();
return result; return result;
} }
@ -100,11 +103,11 @@ int test::fstat(int fd, struct stat* buf) {
static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); } static LONGLONG max_file_size() { return std::numeric_limits<LONGLONG>::max(); }
DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) { DWORD test::GetFileSize(HANDLE hFile, LPDWORD lpFileSizeHigh) {
if (fstat_sim == ERROR) { if (fstat_sim == error) {
SetLastError(ERROR_ACCESS_DENIED); SetLastError(ERROR_ACCESS_DENIED);
return INVALID_FILE_SIZE; return INVALID_FILE_SIZE;
} }
if (fstat_sim == MAX_SIZE) { if (fstat_sim == max_size) {
DWORD max = std::numeric_limits<DWORD>::max(); DWORD max = std::numeric_limits<DWORD>::max();
*lpFileSizeHigh = max >> 1; *lpFileSizeHigh = max >> 1;
return max; return max;
@ -194,15 +197,15 @@ int(test::fileno)(FILE* stream) {
# define EXPECT_EQ_POSIX(expected, actual) # define EXPECT_EQ_POSIX(expected, actual)
#endif #endif
static void write_file(fmt::cstring_view filename, fmt::string_view content) { #if FMT_USE_FCNTL
void write_file(fmt::cstring_view filename, fmt::string_view content) {
fmt::buffered_file f(filename, "w"); fmt::buffered_file f(filename, "w");
f.print("{}", content); f.print("{}", content);
} }
#if FMT_USE_FCNTL
using fmt::file; using fmt::file;
TEST(UtilTest, GetPageSize) { TEST(os_test, getpagesize) {
# ifdef _WIN32 # ifdef _WIN32
SYSTEM_INFO si = {}; SYSTEM_INFO si = {};
GetSystemInfo(&si); GetSystemInfo(&si);
@ -216,7 +219,7 @@ TEST(UtilTest, GetPageSize) {
# endif # endif
} }
TEST(FileTest, OpenRetry) { TEST(file_test, open_retry) {
write_file("temp", "there must be something here"); write_file("temp", "there must be something here");
std::unique_ptr<file> f{nullptr}; std::unique_ptr<file> f{nullptr};
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open, EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
@ -227,7 +230,7 @@ TEST(FileTest, OpenRetry) {
# endif # endif
} }
TEST(FileTest, CloseNoRetryInDtor) { TEST(file_test, close_no_retry_in_dtor) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
std::unique_ptr<file> f(new file(std::move(read_end))); std::unique_ptr<file> f(new file(std::move(read_end)));
@ -240,11 +243,11 @@ TEST(FileTest, CloseNoRetryInDtor) {
saved_close_count = close_count; saved_close_count = close_count;
close_count = 0; close_count = 0;
}, },
format_system_error(EINTR, "cannot close file") + "\n"); system_error_message(EINTR, "cannot close file") + "\n");
EXPECT_EQ(2, saved_close_count); EXPECT_EQ(2, saved_close_count);
} }
TEST(FileTest, CloseNoRetry) { TEST(file_test, close_no_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
close_count = 1; close_count = 1;
@ -253,35 +256,39 @@ TEST(FileTest, CloseNoRetry) {
close_count = 0; close_count = 0;
} }
TEST(FileTest, Size) { TEST(file_test, size) {
std::string content = "top secret, destroy before reading"; std::string content = "top secret, destroy before reading";
write_file("temp", content); write_file("temp", content);
file f("temp", file::RDONLY); file f("temp", file::RDONLY);
EXPECT_GE(f.size(), 0); EXPECT_GE(f.size(), 0);
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size())); EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
# ifdef _WIN32 # ifdef _WIN32
fmt::memory_buffer message; auto error_code = std::error_code();
fmt::detail::format_windows_error(message, ERROR_ACCESS_DENIED, fstat_sim = error;
"cannot get file size"); try {
fstat_sim = ERROR; f.size();
EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message)); } catch (const std::system_error& e) {
fstat_sim = NONE; error_code = e.code();
}
fstat_sim = none;
EXPECT_EQ(error_code,
std::error_code(ERROR_ACCESS_DENIED, fmt::system_category()));
# else # else
f.close(); f.close();
EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes"); EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
# endif # endif
} }
TEST(FileTest, MaxSize) { TEST(file_test, max_size) {
write_file("temp", ""); write_file("temp", "");
file f("temp", file::RDONLY); file f("temp", file::RDONLY);
fstat_sim = MAX_SIZE; fstat_sim = max_size;
EXPECT_GE(f.size(), 0); EXPECT_GE(f.size(), 0);
EXPECT_EQ(max_file_size(), f.size()); EXPECT_EQ(max_file_size(), f.size());
fstat_sim = NONE; fstat_sim = none;
} }
TEST(FileTest, ReadRetry) { TEST(file_test, read_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
enum { SIZE = 4 }; enum { SIZE = 4 };
@ -294,7 +301,7 @@ TEST(FileTest, ReadRetry) {
EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count); EXPECT_EQ_POSIX(static_cast<std::streamsize>(SIZE), count);
} }
TEST(FileTest, WriteRetry) { TEST(file_test, write_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
enum { SIZE = 4 }; enum { SIZE = 4 };
@ -312,7 +319,7 @@ TEST(FileTest, WriteRetry) {
} }
# ifdef _WIN32 # ifdef _WIN32
TEST(FileTest, ConvertReadCount) { TEST(file_test, convert_read_count) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
char c; char c;
@ -320,12 +327,12 @@ TEST(FileTest, ConvertReadCount) {
if (sizeof(unsigned) != sizeof(size_t)) ++size; if (sizeof(unsigned) != sizeof(size_t)) ++size;
read_count = 1; read_count = 1;
read_nbyte = 0; read_nbyte = 0;
EXPECT_THROW(read_end.read(&c, size), fmt::system_error); EXPECT_THROW(read_end.read(&c, size), std::system_error);
read_count = 0; read_count = 0;
EXPECT_EQ(UINT_MAX, read_nbyte); EXPECT_EQ(UINT_MAX, read_nbyte);
} }
TEST(FileTest, ConvertWriteCount) { TEST(file_test, convert_write_count) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
char c; char c;
@ -333,13 +340,13 @@ TEST(FileTest, ConvertWriteCount) {
if (sizeof(unsigned) != sizeof(size_t)) ++size; if (sizeof(unsigned) != sizeof(size_t)) ++size;
write_count = 1; write_count = 1;
write_nbyte = 0; write_nbyte = 0;
EXPECT_THROW(write_end.write(&c, size), fmt::system_error); EXPECT_THROW(write_end.write(&c, size), std::system_error);
write_count = 0; write_count = 0;
EXPECT_EQ(UINT_MAX, write_nbyte); EXPECT_EQ(UINT_MAX, write_nbyte);
} }
# endif # endif
TEST(FileTest, DupNoRetry) { TEST(file_test, dup_no_retry) {
int stdout_fd = FMT_POSIX(fileno(stdout)); int stdout_fd = FMT_POSIX(fileno(stdout));
dup_count = 1; dup_count = 1;
EXPECT_SYSTEM_ERROR( EXPECT_SYSTEM_ERROR(
@ -348,7 +355,7 @@ TEST(FileTest, DupNoRetry) {
dup_count = 0; dup_count = 0;
} }
TEST(FileTest, Dup2Retry) { TEST(file_test, dup2_retry) {
int stdout_fd = FMT_POSIX(fileno(stdout)); int stdout_fd = FMT_POSIX(fileno(stdout));
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2, EXPECT_RETRY(f1.dup2(f2.descriptor()), dup2,
@ -356,21 +363,21 @@ TEST(FileTest, Dup2Retry) {
f1.descriptor(), f2.descriptor())); f1.descriptor(), f2.descriptor()));
} }
TEST(FileTest, Dup2NoExceptRetry) { TEST(file_test, dup2_no_except_retry) {
int stdout_fd = FMT_POSIX(fileno(stdout)); int stdout_fd = FMT_POSIX(fileno(stdout));
file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd); file f1 = file::dup(stdout_fd), f2 = file::dup(stdout_fd);
error_code ec; std::error_code ec;
dup2_count = 1; dup2_count = 1;
f1.dup2(f2.descriptor(), ec); f1.dup2(f2.descriptor(), ec);
# ifndef _WIN32 # ifndef _WIN32
EXPECT_EQ(4, dup2_count); EXPECT_EQ(4, dup2_count);
# else # else
EXPECT_EQ(EINTR, ec.get()); EXPECT_EQ(EINTR, ec.value());
# endif # endif
dup2_count = 0; dup2_count = 0;
} }
TEST(FileTest, PipeNoRetry) { TEST(file_test, pipe_no_retry) {
file read_end, write_end; file read_end, write_end;
pipe_count = 1; pipe_count = 1;
EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR, EXPECT_SYSTEM_ERROR(file::pipe(read_end, write_end), EINTR,
@ -378,7 +385,7 @@ TEST(FileTest, PipeNoRetry) {
pipe_count = 0; pipe_count = 0;
} }
TEST(FileTest, FdopenNoRetry) { TEST(file_test, fdopen_no_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
fdopen_count = 1; fdopen_count = 1;
@ -387,7 +394,7 @@ TEST(FileTest, FdopenNoRetry) {
fdopen_count = 0; fdopen_count = 0;
} }
TEST(BufferedFileTest, OpenRetry) { TEST(buffered_file_test, open_retry) {
write_file("temp", "there must be something here"); write_file("temp", "there must be something here");
std::unique_ptr<buffered_file> f{nullptr}; std::unique_ptr<buffered_file> f{nullptr};
EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen, EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
@ -399,7 +406,7 @@ TEST(BufferedFileTest, OpenRetry) {
# endif # endif
} }
TEST(BufferedFileTest, CloseNoRetryInDtor) { TEST(buffered_file_test, close_no_retry_in_dtor) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r"))); std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
@ -412,11 +419,11 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
saved_fclose_count = fclose_count; saved_fclose_count = fclose_count;
fclose_count = 0; fclose_count = 0;
}, },
format_system_error(EINTR, "cannot close file") + "\n"); system_error_message(EINTR, "cannot close file") + "\n");
EXPECT_EQ(2, saved_fclose_count); EXPECT_EQ(2, saved_fclose_count);
} }
TEST(BufferedFileTest, CloseNoRetry) { TEST(buffered_file_test, close_no_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
buffered_file f = read_end.fdopen("r"); buffered_file f = read_end.fdopen("r");
@ -426,7 +433,7 @@ TEST(BufferedFileTest, CloseNoRetry) {
fclose_count = 0; fclose_count = 0;
} }
TEST(BufferedFileTest, FilenoNoRetry) { TEST(buffered_file_test, fileno_no_retry) {
file read_end, write_end; file read_end, write_end;
file::pipe(read_end, write_end); file::pipe(read_end, write_end);
buffered_file f = read_end.fdopen("r"); buffered_file f = read_end.fdopen("r");
@ -441,9 +448,9 @@ struct test_mock {
static test_mock* instance; static test_mock* instance;
} * test_mock::instance; } * test_mock::instance;
TEST(ScopedMock, Scope) { TEST(scoped_mock, scope) {
{ {
ScopedMock<test_mock> mock; scoped_mock<test_mock> mock;
EXPECT_EQ(&mock, test_mock::instance); EXPECT_EQ(&mock, test_mock::instance);
test_mock& copy = mock; test_mock& copy = mock;
static_cast<void>(copy); static_cast<void>(copy);
@ -453,7 +460,7 @@ TEST(ScopedMock, Scope) {
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
typedef fmt::locale::type locale_type; using locale_type = fmt::locale::type;
struct locale_mock { struct locale_mock {
static locale_mock* instance; static locale_mock* instance;
@ -490,7 +497,8 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
# pragma warning(pop) # pragma warning(pop)
# endif # endif
# if defined(__THROW) && FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408 # if defined(__THROW) && \
((FMT_GCC_VERSION > 0 && FMT_GCC_VERSION <= 408) || defined(__e2k__))
# define FMT_LOCALE_THROW __THROW # define FMT_LOCALE_THROW __THROW
# else # else
# define FMT_LOCALE_THROW # define FMT_LOCALE_THROW
@ -520,20 +528,20 @@ locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
return locale_mock::instance->newlocale(category_mask, locale, base); return locale_mock::instance->newlocale(category_mask, locale, base);
} }
TEST(LocaleTest, LocaleMock) { TEST(locale_test, locale_mock) {
ScopedMock<locale_mock> mock; scoped_mock<locale_mock> mock;
locale_type locale = reinterpret_cast<locale_type>(11); auto locale = reinterpret_cast<locale_type>(11);
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale)); EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
FMT_SYSTEM(newlocale(222, "foo", locale)); FMT_SYSTEM(newlocale(222, "foo", locale));
} }
# endif # endif
TEST(LocaleTest, Locale) { TEST(locale_test, locale) {
# ifndef LC_NUMERIC_MASK # ifndef LC_NUMERIC_MASK
enum { LC_NUMERIC_MASK = LC_NUMERIC }; enum { LC_NUMERIC_MASK = LC_NUMERIC };
# endif # endif
ScopedMock<locale_mock> mock; scoped_mock<locale_mock> mock;
locale_type impl = reinterpret_cast<locale_type>(42); auto impl = reinterpret_cast<locale_type>(42);
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr)) EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
.WillOnce(Return(impl)); .WillOnce(Return(impl));
EXPECT_CALL(mock, freelocale(impl)); EXPECT_CALL(mock, freelocale(impl));
@ -541,8 +549,8 @@ TEST(LocaleTest, Locale) {
EXPECT_EQ(impl, loc.get()); EXPECT_EQ(impl, loc.get());
} }
TEST(LocaleTest, Strtod) { TEST(locale_test, strtod) {
ScopedMock<locale_mock> mock; scoped_mock<locale_mock> mock;
EXPECT_CALL(mock, newlocale(_, _, _)) EXPECT_CALL(mock, newlocale(_, _, _))
.WillOnce(Return(reinterpret_cast<locale_type>(42))); .WillOnce(Return(reinterpret_cast<locale_type>(42)));
EXPECT_CALL(mock, freelocale(_)); EXPECT_CALL(mock, freelocale(_));

View File

@ -11,7 +11,8 @@
#include <climits> #include <climits>
#include <cstring> #include <cstring>
#include "fmt/core.h" #include "fmt/ostream.h"
#include "fmt/xchar.h"
#include "gtest-extra.h" #include "gtest-extra.h"
#include "util.h" #include "util.h"
@ -19,7 +20,7 @@ using fmt::format;
using fmt::format_error; using fmt::format_error;
using fmt::detail::max_value; using fmt::detail::max_value;
const unsigned BIG_NUM = INT_MAX + 1u; const unsigned big_num = INT_MAX + 1u;
// Makes format string argument positional. // Makes format string argument positional.
static std::string make_positional(fmt::string_view format) { static std::string make_positional(fmt::string_view format) {
@ -28,7 +29,7 @@ static std::string make_positional(fmt::string_view format) {
return s; return s;
} }
static std::wstring make_positional(fmt::wstring_view format) { static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
std::wstring s(format.data(), format.size()); std::wstring s(format.data(), format.size());
s.replace(s.find(L'%'), 1, L"%1$"); s.replace(s.find(L'%'), 1, L"%1$");
return s; return s;
@ -41,7 +42,8 @@ std::string test_sprintf(fmt::string_view format, const Args&... args) {
return fmt::sprintf(format, args...); return fmt::sprintf(format, args...);
} }
template <typename... Args> template <typename... Args>
std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) { std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
const Args&... args) {
return fmt::sprintf(format, args...); return fmt::sprintf(format, args...);
} }
@ -50,12 +52,12 @@ std::wstring test_sprintf(fmt::wstring_view format, const Args&... args) {
<< "format: " << format; \ << "format: " << format; \
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg)) EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
TEST(PrintfTest, NoArgs) { TEST(printf_test, no_args) {
EXPECT_EQ("test", test_sprintf("test")); EXPECT_EQ("test", test_sprintf("test"));
EXPECT_EQ(L"test", fmt::sprintf(L"test")); EXPECT_EQ(L"test", fmt::sprintf(L"test"));
} }
TEST(PrintfTest, Escape) { TEST(printf_test, escape) {
EXPECT_EQ("%", test_sprintf("%%")); EXPECT_EQ("%", test_sprintf("%%"));
EXPECT_EQ("before %", test_sprintf("before %%")); EXPECT_EQ("before %", test_sprintf("before %%"));
EXPECT_EQ("% after", test_sprintf("%% after")); EXPECT_EQ("% after", test_sprintf("%% after"));
@ -68,7 +70,7 @@ TEST(PrintfTest, Escape) {
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s")); EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
} }
TEST(PrintfTest, PositionalArgs) { TEST(printf_test, positional_args) {
EXPECT_EQ("42", test_sprintf("%1$d", 42)); EXPECT_EQ("42", test_sprintf("%1$d", 42));
EXPECT_EQ("before 42", test_sprintf("before %1$d", 42)); EXPECT_EQ("before 42", test_sprintf("before %1$d", 42));
EXPECT_EQ("42 after", test_sprintf("%1$d after", 42)); EXPECT_EQ("42 after", test_sprintf("%1$d after", 42));
@ -78,40 +80,40 @@ TEST(PrintfTest, PositionalArgs) {
EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad")); EXPECT_EQ("abracadabra", test_sprintf("%1$s%2$s%1$s", "abra", "cad"));
} }
TEST(PrintfTest, AutomaticArgIndexing) { TEST(printf_test, automatic_arg_indexing) {
EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c')); EXPECT_EQ("abc", test_sprintf("%c%c%c", 'a', 'b', 'c'));
} }
TEST(PrintfTest, NumberIsTooBigInArgIndex) { TEST(printf_test, number_is_too_big_in_arg_index) {
EXPECT_THROW_MSG(test_sprintf(format("%{}$", BIG_NUM)), format_error, EXPECT_THROW_MSG(test_sprintf(format("%{}$", big_num)), format_error,
"number is too big"); "argument not found");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM)), format_error, EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num)), format_error,
"number is too big"); "argument not found");
} }
TEST(PrintfTest, SwitchArgIndexing) { TEST(printf_test, switch_arg_indexing) {
EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error, EXPECT_THROW_MSG(test_sprintf("%1$d%", 1, 2), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", BIG_NUM), 1, 2), EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error, EXPECT_THROW_MSG(test_sprintf("%1$d%d", 1, 2), format_error,
"cannot switch from manual to automatic argument indexing"); "cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error, EXPECT_THROW_MSG(test_sprintf("%d%1$", 1, 2), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", BIG_NUM), 1, 2), format_error, EXPECT_THROW_MSG(test_sprintf(format("%d%{}$d", big_num), 1, 2), format_error,
"number is too big"); "cannot switch from automatic to manual argument indexing");
EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error, EXPECT_THROW_MSG(test_sprintf("%d%1$d", 1, 2), format_error,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
// Indexing errors override width errors. // Indexing errors override width errors.
EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", BIG_NUM), 1, 2), EXPECT_THROW_MSG(test_sprintf(format("%d%1${}d", big_num), 1, 2),
format_error, "number is too big"); format_error, "number is too big");
EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", BIG_NUM), 1, 2), EXPECT_THROW_MSG(test_sprintf(format("%1$d%{}d", big_num), 1, 2),
format_error, "number is too big"); format_error, "number is too big");
} }
TEST(PrintfTest, InvalidArgIndex) { TEST(printf_test, invalid_arg_index) {
EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error, EXPECT_THROW_MSG(test_sprintf("%0$d", 42), format_error,
"argument not found"); "argument not found");
EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error, EXPECT_THROW_MSG(test_sprintf("%2$d", 42), format_error,
@ -120,16 +122,16 @@ TEST(PrintfTest, InvalidArgIndex) {
"argument not found"); "argument not found");
EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found"); EXPECT_THROW_MSG(test_sprintf("%2$", 42), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf(format("%{}$d", BIG_NUM), 42), format_error, EXPECT_THROW_MSG(test_sprintf(format("%{}$d", big_num), 42), format_error,
"number is too big"); "argument not found");
} }
TEST(PrintfTest, DefaultAlignRight) { TEST(printf_test, default_align_right) {
EXPECT_PRINTF(" 42", "%5d", 42); EXPECT_PRINTF(" 42", "%5d", 42);
EXPECT_PRINTF(" abc", "%5s", "abc"); EXPECT_PRINTF(" abc", "%5s", "abc");
} }
TEST(PrintfTest, ZeroFlag) { TEST(printf_test, zero_flag) {
EXPECT_PRINTF("00042", "%05d", 42); EXPECT_PRINTF("00042", "%05d", 42);
EXPECT_PRINTF("-0042", "%05d", -42); EXPECT_PRINTF("-0042", "%05d", -42);
@ -146,7 +148,7 @@ TEST(PrintfTest, ZeroFlag) {
EXPECT_PRINTF(" x", "%05c", 'x'); EXPECT_PRINTF(" x", "%05c", 'x');
} }
TEST(PrintfTest, PlusFlag) { TEST(printf_test, plus_flag) {
EXPECT_PRINTF("+42", "%+d", 42); EXPECT_PRINTF("+42", "%+d", 42);
EXPECT_PRINTF("-42", "%+d", -42); EXPECT_PRINTF("-42", "%+d", -42);
EXPECT_PRINTF("+0042", "%+05d", 42); EXPECT_PRINTF("+0042", "%+05d", 42);
@ -168,7 +170,7 @@ TEST(PrintfTest, PlusFlag) {
EXPECT_PRINTF("x", "% +c", 'x'); EXPECT_PRINTF("x", "% +c", 'x');
} }
TEST(PrintfTest, MinusFlag) { TEST(printf_test, minus_flag) {
EXPECT_PRINTF("abc ", "%-5s", "abc"); EXPECT_PRINTF("abc ", "%-5s", "abc");
EXPECT_PRINTF("abc ", "%0--5s", "abc"); EXPECT_PRINTF("abc ", "%0--5s", "abc");
@ -188,7 +190,7 @@ TEST(PrintfTest, MinusFlag) {
EXPECT_PRINTF(" 42", "%- d", 42); EXPECT_PRINTF(" 42", "%- d", 42);
} }
TEST(PrintfTest, SpaceFlag) { TEST(printf_test, space_flag) {
EXPECT_PRINTF(" 42", "% d", 42); EXPECT_PRINTF(" 42", "% d", 42);
EXPECT_PRINTF("-42", "% d", -42); EXPECT_PRINTF("-42", "% d", -42);
EXPECT_PRINTF(" 0042", "% 05d", 42); EXPECT_PRINTF(" 0042", "% 05d", 42);
@ -198,7 +200,7 @@ TEST(PrintfTest, SpaceFlag) {
EXPECT_PRINTF("x", "% c", 'x'); EXPECT_PRINTF("x", "% c", 'x');
} }
TEST(PrintfTest, HashFlag) { TEST(printf_test, hash_flag) {
EXPECT_PRINTF("042", "%#o", 042); EXPECT_PRINTF("042", "%#o", 042);
EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042); EXPECT_PRINTF(fmt::format("0{:o}", static_cast<unsigned>(-042)), "%#o", -042);
EXPECT_PRINTF("0", "%#o", 0); EXPECT_PRINTF("0", "%#o", 0);
@ -215,7 +217,7 @@ TEST(PrintfTest, HashFlag) {
EXPECT_PRINTF("-42.000000", "%#f", -42.0); EXPECT_PRINTF("-42.000000", "%#f", -42.0);
EXPECT_PRINTF("-42.000000", "%#F", -42.0); EXPECT_PRINTF("-42.000000", "%#F", -42.0);
char buffer[BUFFER_SIZE]; char buffer[256];
safe_sprintf(buffer, "%#e", -42.0); safe_sprintf(buffer, "%#e", -42.0);
EXPECT_PRINTF(buffer, "%#e", -42.0); EXPECT_PRINTF(buffer, "%#e", -42.0);
safe_sprintf(buffer, "%#E", -42.0); safe_sprintf(buffer, "%#E", -42.0);
@ -233,30 +235,30 @@ TEST(PrintfTest, HashFlag) {
EXPECT_PRINTF("x", "%#c", 'x'); EXPECT_PRINTF("x", "%#c", 'x');
} }
TEST(PrintfTest, Width) { TEST(printf_test, width) {
EXPECT_PRINTF(" abc", "%5s", "abc"); EXPECT_PRINTF(" abc", "%5s", "abc");
// Width cannot be specified twice. // Width cannot be specified twice.
EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error, EXPECT_THROW_MSG(test_sprintf("%5-5d", 42), format_error,
"invalid type specifier"); "invalid type specifier");
EXPECT_THROW_MSG(test_sprintf(format("%{}d", BIG_NUM), 42), format_error, EXPECT_THROW_MSG(test_sprintf(format("%{}d", big_num), 42), format_error,
"number is too big"); "number is too big");
EXPECT_THROW_MSG(test_sprintf(format("%1${}d", BIG_NUM), 42), format_error, EXPECT_THROW_MSG(test_sprintf(format("%1${}d", big_num), 42), format_error,
"number is too big"); "number is too big");
} }
TEST(PrintfTest, DynamicWidth) { TEST(printf_test, dynamic_width) {
EXPECT_EQ(" 42", test_sprintf("%*d", 5, 42)); EXPECT_EQ(" 42", test_sprintf("%*d", 5, 42));
EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42)); EXPECT_EQ("42 ", test_sprintf("%*d", -5, 42));
EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%*d", 5.0, 42), format_error,
"width is not integer"); "width is not integer");
EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found"); EXPECT_THROW_MSG(test_sprintf("%*d"), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf("%*d", BIG_NUM, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%*d", big_num, 42), format_error,
"number is too big"); "number is too big");
} }
TEST(PrintfTest, IntPrecision) { TEST(printf_test, int_precision) {
EXPECT_PRINTF("00042", "%.5d", 42); EXPECT_PRINTF("00042", "%.5d", 42);
EXPECT_PRINTF("-00042", "%.5d", -42); EXPECT_PRINTF("-00042", "%.5d", -42);
EXPECT_PRINTF("00042", "%.5x", 0x42); EXPECT_PRINTF("00042", "%.5x", 0x42);
@ -277,8 +279,8 @@ TEST(PrintfTest, IntPrecision) {
EXPECT_PRINTF("00042 ", "%-#10.5o", 042); EXPECT_PRINTF("00042 ", "%-#10.5o", 042);
} }
TEST(PrintfTest, FloatPrecision) { TEST(printf_test, float_precision) {
char buffer[BUFFER_SIZE]; char buffer[256];
safe_sprintf(buffer, "%.3e", 1234.5678); safe_sprintf(buffer, "%.3e", 1234.5678);
EXPECT_PRINTF(buffer, "%.3e", 1234.5678); EXPECT_PRINTF(buffer, "%.3e", 1234.5678);
EXPECT_PRINTF("1234.568", "%.3f", 1234.5678); EXPECT_PRINTF("1234.568", "%.3f", 1234.5678);
@ -287,22 +289,22 @@ TEST(PrintfTest, FloatPrecision) {
EXPECT_PRINTF(buffer, "%.3a", 1234.5678); EXPECT_PRINTF(buffer, "%.3a", 1234.5678);
} }
TEST(PrintfTest, StringPrecision) { TEST(printf_test, string_precision) {
char test[] = {'H', 'e', 'l', 'l', 'o'}; char test[] = {'H', 'e', 'l', 'l', 'o'};
EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell"); EXPECT_EQ(fmt::sprintf("%.4s", test), "Hell");
} }
TEST(PrintfTest, IgnorePrecisionForNonNumericArg) { TEST(printf_test, ignore_precision_for_non_numeric_arg) {
EXPECT_PRINTF("abc", "%.5s", "abc"); EXPECT_PRINTF("abc", "%.5s", "abc");
} }
TEST(PrintfTest, DynamicPrecision) { TEST(printf_test, dynamic_precision) {
EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42)); EXPECT_EQ("00042", test_sprintf("%.*d", 5, 42));
EXPECT_EQ("42", test_sprintf("%.*d", -5, 42)); EXPECT_EQ("42", test_sprintf("%.*d", -5, 42));
EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%.*d", 5.0, 42), format_error,
"precision is not integer"); "precision is not integer");
EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found"); EXPECT_THROW_MSG(test_sprintf("%.*d"), format_error, "argument not found");
EXPECT_THROW_MSG(test_sprintf("%.*d", BIG_NUM, 42), format_error, EXPECT_THROW_MSG(test_sprintf("%.*d", big_num, 42), format_error,
"number is too big"); "number is too big");
if (sizeof(long long) != sizeof(int)) { if (sizeof(long long) != sizeof(int)) {
long long prec = static_cast<long long>(INT_MIN) - 1; long long prec = static_cast<long long>(INT_MIN) - 1;
@ -325,7 +327,7 @@ SPECIALIZE_MAKE_SIGNED(unsigned long long, long long);
// Test length format specifier ``length_spec``. // Test length format specifier ``length_spec``.
template <typename T, typename U> template <typename T, typename U>
void TestLength(const char* length_spec, U value) { void test_length(const char* length_spec, U value) {
long long signed_value = 0; long long signed_value = 0;
unsigned long long unsigned_value = 0; unsigned long long unsigned_value = 0;
// Apply integer promotion to the argument. // Apply integer promotion to the argument.
@ -364,54 +366,54 @@ void TestLength(const char* length_spec, U value) {
EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value); EXPECT_PRINTF(os.str(), fmt::format("%{}X", length_spec), value);
} }
template <typename T> void TestLength(const char* length_spec) { template <typename T> void test_length(const char* length_spec) {
T min = std::numeric_limits<T>::min(), max = max_value<T>(); T min = std::numeric_limits<T>::min(), max = max_value<T>();
TestLength<T>(length_spec, 42); test_length<T>(length_spec, 42);
TestLength<T>(length_spec, -42); test_length<T>(length_spec, -42);
TestLength<T>(length_spec, min); test_length<T>(length_spec, min);
TestLength<T>(length_spec, max); test_length<T>(length_spec, max);
long long long_long_min = std::numeric_limits<long long>::min(); long long long_long_min = std::numeric_limits<long long>::min();
if (static_cast<long long>(min) > long_long_min) if (static_cast<long long>(min) > long_long_min)
TestLength<T>(length_spec, static_cast<long long>(min) - 1); test_length<T>(length_spec, static_cast<long long>(min) - 1);
unsigned long long long_long_max = max_value<long long>(); unsigned long long long_long_max = max_value<long long>();
if (static_cast<unsigned long long>(max) < long_long_max) if (static_cast<unsigned long long>(max) < long_long_max)
TestLength<T>(length_spec, static_cast<long long>(max) + 1); test_length<T>(length_spec, static_cast<long long>(max) + 1);
TestLength<T>(length_spec, std::numeric_limits<short>::min()); test_length<T>(length_spec, std::numeric_limits<short>::min());
TestLength<T>(length_spec, max_value<unsigned short>()); test_length<T>(length_spec, max_value<unsigned short>());
TestLength<T>(length_spec, std::numeric_limits<int>::min()); test_length<T>(length_spec, std::numeric_limits<int>::min());
TestLength<T>(length_spec, max_value<int>()); test_length<T>(length_spec, max_value<int>());
TestLength<T>(length_spec, std::numeric_limits<unsigned>::min()); test_length<T>(length_spec, std::numeric_limits<unsigned>::min());
TestLength<T>(length_spec, max_value<unsigned>()); test_length<T>(length_spec, max_value<unsigned>());
TestLength<T>(length_spec, std::numeric_limits<long long>::min()); test_length<T>(length_spec, std::numeric_limits<long long>::min());
TestLength<T>(length_spec, max_value<long long>()); test_length<T>(length_spec, max_value<long long>());
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min()); test_length<T>(length_spec, std::numeric_limits<unsigned long long>::min());
TestLength<T>(length_spec, max_value<unsigned long long>()); test_length<T>(length_spec, max_value<unsigned long long>());
} }
TEST(PrintfTest, Length) { TEST(printf_test, length) {
TestLength<char>("hh"); test_length<char>("hh");
TestLength<signed char>("hh"); test_length<signed char>("hh");
TestLength<unsigned char>("hh"); test_length<unsigned char>("hh");
TestLength<short>("h"); test_length<short>("h");
TestLength<unsigned short>("h"); test_length<unsigned short>("h");
TestLength<long>("l"); test_length<long>("l");
TestLength<unsigned long>("l"); test_length<unsigned long>("l");
TestLength<long long>("ll"); test_length<long long>("ll");
TestLength<unsigned long long>("ll"); test_length<unsigned long long>("ll");
TestLength<intmax_t>("j"); test_length<intmax_t>("j");
TestLength<size_t>("z"); test_length<size_t>("z");
TestLength<std::ptrdiff_t>("t"); test_length<std::ptrdiff_t>("t");
long double max = max_value<long double>(); long double max = max_value<long double>();
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max); EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max); EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
} }
TEST(PrintfTest, Bool) { TEST(printf_test, bool) {
EXPECT_PRINTF("1", "%d", true); EXPECT_PRINTF("1", "%d", true);
EXPECT_PRINTF("true", "%s", true); EXPECT_PRINTF("true", "%s", true);
} }
TEST(PrintfTest, Int) { TEST(printf_test, int) {
EXPECT_PRINTF("-42", "%d", -42); EXPECT_PRINTF("-42", "%d", -42);
EXPECT_PRINTF("-42", "%i", -42); EXPECT_PRINTF("-42", "%i", -42);
unsigned u = 0 - 42u; unsigned u = 0 - 42u;
@ -421,20 +423,20 @@ TEST(PrintfTest, Int) {
EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42); EXPECT_PRINTF(fmt::format("{:X}", u), "%X", -42);
} }
TEST(PrintfTest, long_long) { TEST(printf_test, long_long) {
// fmt::printf allows passing long long arguments to %d without length // fmt::printf allows passing long long arguments to %d without length
// specifiers. // specifiers.
long long max = max_value<long long>(); long long max = max_value<long long>();
EXPECT_PRINTF(fmt::format("{}", max), "%d", max); EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
} }
TEST(PrintfTest, Float) { TEST(printf_test, float) {
EXPECT_PRINTF("392.650000", "%f", 392.65); EXPECT_PRINTF("392.650000", "%f", 392.65);
EXPECT_PRINTF("392.65", "%.2f", 392.65); EXPECT_PRINTF("392.65", "%.2f", 392.65);
EXPECT_PRINTF("392.6", "%.1f", 392.65); EXPECT_PRINTF("392.6", "%.1f", 392.65);
EXPECT_PRINTF("393", "%.f", 392.65); EXPECT_PRINTF("393", "%.f", 392.65);
EXPECT_PRINTF("392.650000", "%F", 392.65); EXPECT_PRINTF("392.650000", "%F", 392.65);
char buffer[BUFFER_SIZE]; char buffer[256];
safe_sprintf(buffer, "%e", 392.65); safe_sprintf(buffer, "%e", 392.65);
EXPECT_PRINTF(buffer, "%e", 392.65); EXPECT_PRINTF(buffer, "%e", 392.65);
safe_sprintf(buffer, "%E", 392.65); safe_sprintf(buffer, "%E", 392.65);
@ -450,7 +452,7 @@ TEST(PrintfTest, Float) {
EXPECT_EQ(buffer, format("{:A}", -392.65)); EXPECT_EQ(buffer, format("{:A}", -392.65));
} }
TEST(PrintfTest, Inf) { TEST(printf_test, inf) {
double inf = std::numeric_limits<double>::infinity(); double inf = std::numeric_limits<double>::infinity();
for (const char* type = "fega"; *type; ++type) { for (const char* type = "fega"; *type; ++type) {
EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf); EXPECT_PRINTF("inf", fmt::format("%{}", *type), inf);
@ -459,7 +461,7 @@ TEST(PrintfTest, Inf) {
} }
} }
TEST(PrintfTest, Char) { TEST(printf_test, char) {
EXPECT_PRINTF("x", "%c", 'x'); EXPECT_PRINTF("x", "%c", 'x');
int max = max_value<int>(); int max = max_value<int>();
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max); EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
@ -468,7 +470,7 @@ TEST(PrintfTest, Char) {
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max); EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
} }
TEST(PrintfTest, String) { TEST(printf_test, string) {
EXPECT_PRINTF("abc", "%s", "abc"); EXPECT_PRINTF("abc", "%s", "abc");
const char* null_str = nullptr; const char* null_str = nullptr;
EXPECT_PRINTF("(null)", "%s", null_str); EXPECT_PRINTF("(null)", "%s", null_str);
@ -479,13 +481,13 @@ TEST(PrintfTest, String) {
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr); EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
} }
TEST(PrintfTest, UCharString) { TEST(printf_test, uchar_string) {
unsigned char str[] = "test"; unsigned char str[] = "test";
unsigned char* pstr = str; unsigned char* pstr = str;
EXPECT_EQ("test", fmt::sprintf("%s", pstr)); EXPECT_EQ("test", fmt::sprintf("%s", pstr));
} }
TEST(PrintfTest, Pointer) { TEST(printf_test, pointer) {
int n; int n;
void* p = &n; void* p = &n;
EXPECT_PRINTF(fmt::format("{}", p), "%p", p); EXPECT_PRINTF(fmt::format("{}", p), "%p", p);
@ -508,20 +510,16 @@ TEST(PrintfTest, Pointer) {
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr); EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
} }
TEST(PrintfTest, Location) {
// TODO: test %n
}
enum test_enum { answer = 42 }; enum test_enum { answer = 42 };
TEST(PrintfTest, Enum) { TEST(printf_test, enum) {
EXPECT_PRINTF("42", "%d", answer); EXPECT_PRINTF("42", "%d", answer);
volatile test_enum volatile_enum = answer; volatile test_enum volatile_enum = answer;
EXPECT_PRINTF("42", "%d", volatile_enum); EXPECT_PRINTF("42", "%d", volatile_enum);
} }
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
TEST(PrintfTest, Examples) { TEST(printf_test, examples) {
const char* weekday = "Thursday"; const char* weekday = "Thursday";
const char* month = "August"; const char* month = "August";
int day = 21; int day = 21;
@ -529,7 +527,7 @@ TEST(PrintfTest, Examples) {
"Thursday, 21 August"); "Thursday, 21 August");
} }
TEST(PrintfTest, PrintfError) { TEST(printf_test, printf_error) {
fmt::file read_end, write_end; fmt::file read_end, write_end;
fmt::file::pipe(read_end, write_end); fmt::file::pipe(read_end, write_end);
int result = fmt::fprintf(read_end.fdopen("r").get(), "test"); int result = fmt::fprintf(read_end.fdopen("r").get(), "test");
@ -537,26 +535,20 @@ TEST(PrintfTest, PrintfError) {
} }
#endif #endif
TEST(PrintfTest, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); } TEST(printf_test, wide_string) {
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
TEST(PrintfTest, PrintfCustom) {
EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
} }
TEST(PrintfTest, OStream) { TEST(printf_test, printf_custom) {
std::ostringstream os; EXPECT_EQ("abc", test_sprintf("%s", test_string("abc")));
int ret = fmt::fprintf(os, "Don't %s!", "panic");
EXPECT_EQ("Don't panic!", os.str());
EXPECT_EQ(12, ret);
} }
TEST(PrintfTest, VPrintf) { TEST(printf_test, vprintf) {
fmt::format_arg_store<fmt::printf_context, int> as{42}; fmt::format_arg_store<fmt::printf_context, int> as{42};
fmt::basic_format_args<fmt::printf_context> args(as); fmt::basic_format_args<fmt::printf_context> args(as);
EXPECT_EQ(fmt::vsprintf("%d", args), "42"); EXPECT_EQ(fmt::vsprintf("%d", args), "42");
EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42"); EXPECT_WRITE(stdout, fmt::vprintf("%d", args), "42");
EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42"); EXPECT_WRITE(stdout, fmt::vfprintf(stdout, "%d", args), "42");
EXPECT_WRITE(stdout, fmt::vfprintf(std::cout, "%d", args), "42");
} }
template <typename... Args> template <typename... Args>
@ -564,15 +556,15 @@ void check_format_string_regression(fmt::string_view s, const Args&... args) {
fmt::sprintf(s, args...); fmt::sprintf(s, args...);
} }
TEST(PrintfTest, CheckFormatStringRegression) { TEST(printf_test, check_format_string_regression) {
check_format_string_regression("%c%s", 'x', ""); check_format_string_regression("%c%s", 'x', "");
} }
TEST(PrintfTest, FixedLargeExponent) { TEST(printf_test, fixed_large_exponent) {
EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21)); EXPECT_EQ("1000000000000000000000", fmt::sprintf("%.*f", -13, 1e21));
} }
TEST(PrintfTest, VSPrintfMakeArgsExample) { TEST(printf_test, vsprintf_make_args_example) {
fmt::format_arg_store<fmt::printf_context, int, const char*> as{42, fmt::format_arg_store<fmt::printf_context, int, const char*> as{42,
"something"}; "something"};
fmt::basic_format_args<fmt::printf_context> args(as); fmt::basic_format_args<fmt::printf_context> args(as);
@ -581,15 +573,12 @@ TEST(PrintfTest, VSPrintfMakeArgsExample) {
fmt::basic_format_args<fmt::printf_context> args2(as2); fmt::basic_format_args<fmt::printf_context> args2(as2);
EXPECT_EQ("[42] something happened", EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened", args2)); fmt::vsprintf("[%d] %s happened", args2));
// The older gcc versions can't cast the return value.
#if !defined(__GNUC__) || (__GNUC__ > 4)
EXPECT_EQ("[42] something happened", EXPECT_EQ("[42] something happened",
fmt::vsprintf("[%d] %s happened", fmt::vsprintf("[%d] %s happened",
{fmt::make_printf_args(42, "something")})); {fmt::make_printf_args(42, "something")}));
#endif
} }
TEST(PrintfTest, VSPrintfMakeWArgsExample) { TEST(printf_test, vsprintf_make_wargs_example) {
fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{ fmt::format_arg_store<fmt::wprintf_context, int, const wchar_t*> as{
42, L"something"}; 42, L"something"};
fmt::basic_format_args<fmt::wprintf_context> args(as); fmt::basic_format_args<fmt::wprintf_context> args(as);
@ -599,30 +588,7 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) {
fmt::basic_format_args<fmt::wprintf_context> args2(as2); fmt::basic_format_args<fmt::wprintf_context> args2(as2);
EXPECT_EQ(L"[42] something happened", EXPECT_EQ(L"[42] something happened",
fmt::vsprintf(L"[%d] %s happened", args2)); fmt::vsprintf(L"[%d] %s happened", args2));
// the older gcc versions can't cast the return value
#if !defined(__GNUC__) || (__GNUC__ > 4)
EXPECT_EQ(L"[42] something happened", EXPECT_EQ(L"[42] something happened",
fmt::vsprintf(L"[%d] %s happened", fmt::vsprintf(L"[%d] %s happened",
{fmt::make_wprintf_args(42, L"something")})); {fmt::make_wprintf_args(42, L"something")}));
#endif
}
TEST(PrintfTest, PrintfDetermineOutputSize) {
using backit = std::back_insert_iterator<std::vector<char>>;
using truncated_printf_context =
fmt::basic_printf_context<fmt::detail::truncating_iterator<backit>, char>;
auto v = std::vector<char>{};
auto it = std::back_inserter(v);
const auto format_string = "%s";
const auto format_arg = "Hello";
const auto expected_size = fmt::sprintf(format_string, format_arg).size();
EXPECT_EQ((truncated_printf_context(
fmt::detail::truncating_iterator<backit>(it, 0), format_string,
fmt::make_format_args<truncated_printf_context>(format_arg))
.format()
.count()),
expected_size);
} }

View File

@ -11,106 +11,104 @@
#include "fmt/ranges.h" #include "fmt/ranges.h"
#include "gtest.h"
// Check if 'if constexpr' is supported.
#if (__cplusplus > 201402L) || \
(defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
# include <array>
#include <map> #include <map>
#include <string> #include <string>
#include <vector> #include <vector>
TEST(RangesTest, FormatVector) { #include "gtest/gtest.h"
std::vector<int32_t> iv{1, 2, 3, 5, 7, 11};
auto ivf = fmt::format("{}", iv); #if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 601
EXPECT_EQ("{1, 2, 3, 5, 7, 11}", ivf); # define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
#endif
#if !FMT_MSC_VER || FMT_MSC_VER > 1910
# define FMT_RANGES_TEST_ENABLE_JOIN
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
#endif
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
TEST(ranges_test, format_array) {
int arr[] = {1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", arr), "[1, 2, 3, 5, 7, 11]");
} }
TEST(RangesTest, FormatVector2) { TEST(ranges_test, format_2d_array) {
std::vector<std::vector<int32_t>> ivv{{1, 2}, {3, 5}, {7, 11}}; int arr[][2] = {{1, 2}, {3, 5}, {7, 11}};
auto ivf = fmt::format("{}", ivv); EXPECT_EQ(fmt::format("{}", arr), "[[1, 2], [3, 5], [7, 11]]");
EXPECT_EQ("{{1, 2}, {3, 5}, {7, 11}}", ivf);
} }
TEST(RangesTest, FormatMap) { TEST(ranges_test, format_array_of_literals) {
std::map<std::string, int32_t> simap{{"one", 1}, {"two", 2}}; const char* arr[] = {"1234", "abcd"};
EXPECT_EQ("{(\"one\", 1), (\"two\", 2)}", fmt::format("{}", simap)); EXPECT_EQ(fmt::format("{}", arr), "[\"1234\", \"abcd\"]");
}
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
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]");
} }
TEST(RangesTest, FormatPair) { TEST(ranges_test, format_vector2) {
std::pair<int64_t, float> pa1{42, 1.5f}; auto v = std::vector<std::vector<int>>{{1, 2}, {3, 5}, {7, 11}};
EXPECT_EQ("(42, 1.5)", fmt::format("{}", pa1)); EXPECT_EQ(fmt::format("{}", v), "[[1, 2], [3, 5], [7, 11]]");
} }
TEST(RangesTest, FormatTuple) { TEST(ranges_test, format_map) {
std::tuple<int64_t, float, std::string, char> t{42, 1.5f, "this is tuple", auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
'i'}; EXPECT_EQ(fmt::format("{}", m), "[(\"one\", 1), (\"two\", 2)]");
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t));
EXPECT_EQ("()", fmt::format("{}", std::tuple<>()));
} }
TEST(RangesTest, JoinTuple) { TEST(ranges_test, format_pair) {
// Value tuple args auto p = std::pair<int, float>(42, 1.5f);
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f); EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));
// Testing lvalue tuple args
int x = 4;
std::tuple<char, int&> t2{'b', x};
EXPECT_EQ("b + 4", fmt::format("{}", fmt::join(t2, " + ")));
// Empty tuple
std::tuple<> t3;
EXPECT_EQ("", fmt::format("{}", fmt::join(t3, "|")));
// Single element tuple
std::tuple<float> t4{4.0f};
EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
} }
TEST(RangesTest, JoinInitializerList) { TEST(ranges_test, format_tuple) {
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", "))); auto t =
EXPECT_EQ("fmt rocks !", std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " "))); EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
} }
struct my_struct { #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
int32_t i; struct tuple_like {
std::string str; // can throw int i;
template <size_t N> decltype(auto) get() const noexcept { std::string str;
if constexpr (N == 0)
template <size_t N> fmt::enable_if_t<N == 0, int> get() const noexcept {
return i; return i;
else if constexpr (N == 1) }
return fmt::string_view{str}; template <size_t N>
fmt::enable_if_t<N == 1, fmt::string_view> get() const noexcept {
return str;
} }
}; };
template <size_t N> decltype(auto) get(const my_struct& s) noexcept { template <size_t N>
return s.get<N>(); auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
return t.get<N>();
} }
namespace std { namespace std {
template <>
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
template <> struct tuple_size<my_struct> : std::integral_constant<size_t, 2> {}; template <size_t N> struct tuple_element<N, tuple_like> {
using type = decltype(std::declval<tuple_like>().get<N>());
template <size_t N> struct tuple_element<N, my_struct> {
using type = decltype(std::declval<my_struct>().get<N>());
}; };
} // namespace std } // namespace std
TEST(RangesTest, FormatStruct) { TEST(ranges_test, format_struct) {
my_struct mst{13, "my struct"}; auto t = tuple_like{42, "foo"};
EXPECT_EQ("(13, \"my struct\")", fmt::format("{}", mst)); EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
} }
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(RangesTest, FormatTo) { TEST(ranges_test, format_to) {
char buf[10]; char buf[10];
auto end = fmt::format_to(buf, "{}", std::vector{1, 2, 3}); auto end = fmt::format_to(buf, "{}", std::vector<int>{1, 2, 3});
*end = '\0'; *end = '\0';
EXPECT_STREQ(buf, "{1, 2, 3}"); EXPECT_STREQ(buf, "[1, 2, 3]");
} }
struct path_like { struct path_like {
@ -120,13 +118,10 @@ struct path_like {
operator std::string() const; operator std::string() const;
}; };
TEST(RangesTest, PathLike) { TEST(ranges_test, path_like) {
EXPECT_FALSE((fmt::is_range<path_like, char>::value)); EXPECT_FALSE((fmt::is_range<path_like, char>::value));
} }
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
// 201402L && _MSC_VER >= 1910)
#ifdef FMT_USE_STRING_VIEW #ifdef FMT_USE_STRING_VIEW
struct string_like { struct string_like {
const char* begin(); const char* begin();
@ -135,21 +130,135 @@ struct string_like {
explicit operator std::string_view() const { return "foo"; } explicit operator std::string_view() const { return "foo"; }
}; };
TEST(RangesTest, FormatStringLike) { TEST(ranges_test, format_string_like) {
EXPECT_EQ("foo", fmt::format("{}", string_like())); EXPECT_EQ(fmt::format("{}", string_like()), "foo");
} }
#endif // FMT_USE_STRING_VIEW #endif // FMT_USE_STRING_VIEW
// A range that provides non-const only begin()/end() to test fmt::join handles
// that.
//
// Some ranges (e.g. those produced by range-v3's views::filter()) can cache
// information during iteration so they only provide non-const begin()/end().
template <typename T> class non_const_only_range {
private:
std::vector<T> vec;
public:
using const_iterator = typename ::std::vector<T>::const_iterator;
template <typename... Args>
explicit non_const_only_range(Args&&... args)
: vec(std::forward<Args>(args)...) {}
const_iterator begin() { return vec.begin(); }
const_iterator end() { return vec.end(); }
};
template <typename T> class noncopyable_range {
private:
std::vector<T> vec;
public:
using const_iterator = typename ::std::vector<T>::const_iterator;
template <typename... Args>
explicit noncopyable_range(Args&&... args)
: vec(std::forward<Args>(args)...) {}
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(); }
};
TEST(ranges_test, range) {
noncopyable_range<int> w(3u, 0);
EXPECT_EQ(fmt::format("{}", w), "[0, 0, 0]");
EXPECT_EQ(fmt::format("{}", noncopyable_range<int>(3u, 0)), "[0, 0, 0]");
non_const_only_range<int> x(3u, 0);
EXPECT_EQ(fmt::format("{}", x), "[0, 0, 0]");
EXPECT_EQ(fmt::format("{}", non_const_only_range<int>(3u, 0)), "[0, 0, 0]");
auto y = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", y), "[0, 0, 0]");
EXPECT_EQ(fmt::format("{}", std::vector<int>(3u, 0)), "[0, 0, 0]");
const auto z = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", z), "[0, 0, 0]");
}
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
struct unformattable {};
TEST(ranges_test, unformattable_range) {
EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,
fmt::format_context>::value));
}
#endif
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
TEST(ranges_test, join_tuple) {
// Value tuple args.
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
EXPECT_EQ(fmt::format("({})", fmt::join(t1, ", ")), "(a, 1, 2)");
// Testing lvalue tuple args.
int x = 4;
auto t2 = std::tuple<char, int&>('b', x);
EXPECT_EQ(fmt::format("{}", fmt::join(t2, " + ")), "b + 4");
// Empty tuple.
auto t3 = std::tuple<>();
EXPECT_EQ(fmt::format("{}", fmt::join(t3, "|")), "");
// Single element tuple.
auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
}
TEST(ranges_test, join_initializer_list) {
EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3");
EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")),
"fmt rocks !");
}
struct zstring_sentinel {}; struct zstring_sentinel {};
bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; }
bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; }
struct zstring { struct zstring {
const char* p; const char* p;
const char* begin() const { return p; } const char* begin() const { return p; }
zstring_sentinel end() const { return {}; } zstring_sentinel end() const { return {}; }
}; };
TEST(RangesTest, JoinSentinel) {
zstring hello{"hello"}; TEST(ranges_test, join_sentinel) {
EXPECT_EQ("{'h', 'e', 'l', 'l', 'o'}", fmt::format("{}", hello)); auto hello = zstring{"hello"};
EXPECT_EQ("h_e_l_l_o", fmt::format("{}", fmt::join(hello, "_"))); EXPECT_EQ(fmt::format("{}", hello), "['h', 'e', 'l', 'l', 'o']");
EXPECT_EQ(fmt::format("{}", fmt::join(hello, "_")), "h_e_l_l_o");
} }
TEST(ranges_test, join_range) {
noncopyable_range<int> w(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(w, ",")), "0,0,0");
EXPECT_EQ(fmt::format("{}", fmt::join(noncopyable_range<int>(3u, 0), ",")),
"0,0,0");
non_const_only_range<int> x(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(x, ",")), "0,0,0");
EXPECT_EQ(fmt::format("{}", fmt::join(non_const_only_range<int>(3u, 0), ",")),
"0,0,0");
auto y = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(y, ",")), "0,0,0");
EXPECT_EQ(fmt::format("{}", fmt::join(std::vector<int>(3u, 0), ",")),
"0,0,0");
const auto z = std::vector<int>(3u, 0);
EXPECT_EQ(fmt::format("{}", fmt::join(z, ",")), "0,0,0");
}
#endif // FMT_RANGES_TEST_ENABLE_JOIN

View File

@ -11,25 +11,25 @@
#include <climits> #include <climits>
#include "gmock.h" #include "gmock/gmock.h"
#include "gtest-extra.h" #include "gtest-extra.h"
TEST(ScanTest, ReadText) { TEST(scan_test, read_text) {
fmt::string_view s = "foo"; auto s = fmt::string_view("foo");
auto end = fmt::scan(s, "foo"); auto end = fmt::scan(s, "foo");
EXPECT_EQ(end, s.end()); EXPECT_EQ(end, s.end());
EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input"); EXPECT_THROW_MSG(fmt::scan("fob", "foo"), fmt::format_error, "invalid input");
} }
TEST(ScanTest, ReadInt) { TEST(scan_test, read_int) {
int n = 0; auto n = int();
fmt::scan("42", "{}", n); fmt::scan("42", "{}", n);
EXPECT_EQ(n, 42); EXPECT_EQ(n, 42);
fmt::scan("-42", "{}", n); fmt::scan("-42", "{}", n);
EXPECT_EQ(n, -42); EXPECT_EQ(n, -42);
} }
TEST(ScanTest, ReadLongLong) { TEST(scan_test, read_longlong) {
long long n = 0; long long n = 0;
fmt::scan("42", "{}", n); fmt::scan("42", "{}", n);
EXPECT_EQ(n, 42); EXPECT_EQ(n, 42);
@ -37,15 +37,15 @@ TEST(ScanTest, ReadLongLong) {
EXPECT_EQ(n, -42); EXPECT_EQ(n, -42);
} }
TEST(ScanTest, ReadUInt) { TEST(scan_test, read_uint) {
unsigned n = 0; auto n = unsigned();
fmt::scan("42", "{}", n); fmt::scan("42", "{}", n);
EXPECT_EQ(n, 42); EXPECT_EQ(n, 42);
EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error, EXPECT_THROW_MSG(fmt::scan("-42", "{}", n), fmt::format_error,
"invalid input"); "invalid input");
} }
TEST(ScanTest, ReadULongLong) { TEST(scan_test, read_ulonglong) {
unsigned long long n = 0; unsigned long long n = 0;
fmt::scan("42", "{}", n); fmt::scan("42", "{}", n);
EXPECT_EQ(n, 42); EXPECT_EQ(n, 42);
@ -53,14 +53,14 @@ TEST(ScanTest, ReadULongLong) {
"invalid input"); "invalid input");
} }
TEST(ScanTest, ReadString) { TEST(scan_test, read_string) {
std::string s; auto s = std::string();
fmt::scan("foo", "{}", s); fmt::scan("foo", "{}", s);
EXPECT_EQ(s, "foo"); EXPECT_EQ(s, "foo");
} }
TEST(ScanTest, ReadStringView) { TEST(scan_test, read_string_view) {
fmt::string_view s; auto s = fmt::string_view();
fmt::scan("foo", "{}", s); fmt::scan("foo", "{}", s);
EXPECT_EQ(s, "foo"); EXPECT_EQ(s, "foo");
} }
@ -90,8 +90,8 @@ template <> struct scanner<tm> {
}; };
} // namespace fmt } // namespace fmt
TEST(ScanTest, ReadCustom) { TEST(scan_test, read_custom) {
const char* input = "Date: 1985-10-25"; auto input = "Date: 1985-10-25";
auto t = tm(); auto t = tm();
fmt::scan(input, "Date: {0:%Y-%m-%d}", t); fmt::scan(input, "Date: {0:%Y-%m-%d}", t);
EXPECT_EQ(t.tm_year, 85); EXPECT_EQ(t.tm_year, 85);
@ -100,16 +100,16 @@ TEST(ScanTest, ReadCustom) {
} }
#endif #endif
TEST(ScanTest, InvalidFormat) { TEST(scan_test, invalid_format) {
EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error, EXPECT_THROW_MSG(fmt::scan("", "{}"), fmt::format_error,
"argument index out of range"); "argument index out of range");
EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error, EXPECT_THROW_MSG(fmt::scan("", "{"), fmt::format_error,
"invalid format string"); "invalid format string");
} }
TEST(ScanTest, Example) { TEST(scan_test, example) {
std::string key; auto key = std::string();
int value; auto value = int();
fmt::scan("answer = 42", "{} = {}", key, value); fmt::scan("answer = 42", "{} = {}", key, value);
EXPECT_EQ(key, "answer"); EXPECT_EQ(key, "answer");
EXPECT_EQ(value, 42); EXPECT_EQ(value, 42);

View File

@ -169,13 +169,16 @@ struct scan_handler : error_handler {
scan_ctx_.advance_to(it + size); scan_ctx_.advance_to(it + size);
} }
int on_arg_id() { return on_arg_id(next_arg_id_++); } FMT_CONSTEXPR int on_arg_id() { return on_arg_id(next_arg_id_++); }
int on_arg_id(int id) { FMT_CONSTEXPR int on_arg_id(int id) {
if (id >= args_.size) on_error("argument index out of range"); if (id >= args_.size) on_error("argument index out of range");
arg_ = args_.data[id]; arg_ = args_.data[id];
return id; return id;
} }
int on_arg_id(string_view) { return on_error("invalid format"), 0; } FMT_CONSTEXPR int on_arg_id(string_view id) {
if (id.data()) on_error("invalid format");
return 0;
}
void on_replacement_field(int, const char*) { void on_replacement_field(int, const char*) {
auto it = scan_ctx_.begin(), end = scan_ctx_.end(); auto it = scan_ctx_.begin(), end = scan_ctx_.end();

View File

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.1...3.18)
project(fmt-link CXX)
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE)
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
# Broken LTO on GCC 4
if (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
set(BROKEN_LTO ON)
endif ()
if (NOT BROKEN_LTO AND CMAKE_VERSION VERSION_GREATER "3.8")
# CMake 3.9+
include(CheckIPOSupported)
check_ipo_supported(RESULT HAVE_IPO)
if (HAVE_IPO)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif ()
endif ()
add_subdirectory(../.. fmt)
set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON)
add_library(library-test SHARED library.cc)
target_link_libraries(library-test PRIVATE fmt::fmt)
add_executable(exe-test main.cc)
target_link_libraries(exe-test PRIVATE library-test)

View File

@ -0,0 +1,5 @@
#include <fmt/compile.h>
__attribute__((visibility("default"))) std::string foo() {
return fmt::format(FMT_COMPILE("foo bar {}"), 4242);
}

View File

@ -0,0 +1,6 @@
#include <iostream>
#include <string>
extern std::string foo();
int main() { std::cout << foo() << std::endl; }

View File

@ -1,14 +1,14 @@
#include <format> #include <format>
#include "gtest.h" #include "gtest/gtest.h"
TEST(StdFormatTest, Escaping) { TEST(std_format_test, escaping) {
using namespace std; using namespace std;
string s = format("{0}-{{", 8); // s == "8-{" string s = format("{0}-{{", 8); // s == "8-{"
EXPECT_EQ(s, "8-{"); EXPECT_EQ(s, "8-{");
} }
TEST(StdFormatTest, Indexing) { TEST(std_format_test, indexing) {
using namespace std; using namespace std;
string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing
string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing
@ -20,7 +20,7 @@ TEST(StdFormatTest, Indexing) {
EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error); EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error);
} }
TEST(StdFormatTest, Alignment) { TEST(std_format_test, alignment) {
using namespace std; using namespace std;
char c = 120; char c = 120;
string s0 = format("{:6}", 42); // s0 == " 42" string s0 = format("{:6}", 42); // s0 == " 42"
@ -41,7 +41,7 @@ TEST(StdFormatTest, Alignment) {
EXPECT_EQ(s7, "true "); EXPECT_EQ(s7, "true ");
} }
TEST(StdFormatTest, Float) { TEST(std_format_test, float) {
using namespace std; using namespace std;
double inf = numeric_limits<double>::infinity(); double inf = numeric_limits<double>::infinity();
double nan = numeric_limits<double>::quiet_NaN(); double nan = numeric_limits<double>::quiet_NaN();
@ -57,7 +57,7 @@ TEST(StdFormatTest, Float) {
EXPECT_EQ(s3, "nan +nan nan nan"); EXPECT_EQ(s3, "nan +nan nan nan");
} }
TEST(StdFormatTest, Int) { TEST(std_format_test, int) {
using namespace std; using namespace std;
string s0 = format("{}", 42); // s0 == "42" string s0 = format("{}", 42); // s0 == "42"
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a" string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
@ -83,7 +83,7 @@ template <> struct std::formatter<color> : std::formatter<const char*> {
struct err {}; struct err {};
TEST(StdFormatTest, Formatter) { TEST(std_format_test, formatter) {
std::string s0 = std::format("{}", 42); // OK: library-provided formatter std::string s0 = std::format("{}", 42); // OK: library-provided formatter
// std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled // std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled
// formatter // formatter
@ -103,15 +103,19 @@ template <> struct std::formatter<S> {
// Parses a width argument id in the format { <digit> }. // Parses a width argument id in the format { <digit> }.
constexpr auto parse(format_parse_context& ctx) { constexpr auto parse(format_parse_context& ctx) {
constexpr auto is_ascii_digit = [](const char c) {
return c >= '0' && c <= '9';
};
auto iter = ctx.begin(); auto iter = ctx.begin();
// auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; }; // auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; };
auto get_char = [&]() { return iter != ctx.end() ? *iter : '\0'; }; auto get_char = [&]() { return iter != ctx.end() ? *iter : '\0'; };
if (get_char() != '{') return iter; if (get_char() != '{') return iter;
++iter; ++iter;
char c = get_char(); char c = get_char();
if (!isdigit(c) || (++iter, get_char()) != '}') if (!is_ascii_digit(c) || (++iter, get_char()) != '}')
throw format_error("invalid format"); throw format_error("invalid format");
width_arg_id = c - '0'; width_arg_id = fmt::detail::to_unsigned(c - '0');
ctx.check_arg_id(width_arg_id); ctx.check_arg_id(width_arg_id);
return ++iter; return ++iter;
} }
@ -135,7 +139,7 @@ template <> struct std::formatter<S> {
} }
}; };
TEST(StdFormatTest, Parsing) { TEST(std_format_test, parsing) {
std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42" std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42"
EXPECT_EQ(s, " 42"); EXPECT_EQ(s, " 42");
} }
@ -149,7 +153,7 @@ template <> struct std::formatter<__int128_t> : std::formatter<long long> {
} }
}; };
TEST(StdFormatTest, Int128) { TEST(std_format_test, int128) {
__int128_t n = 42; __int128_t n = 42;
auto s = std::format("{}", n); auto s = std::format("{}", n);
EXPECT_EQ(s, "42"); EXPECT_EQ(s, "42");

View File

@ -10,7 +10,11 @@
#include <stdexcept> #include <stdexcept>
#include "gtest.h" void throw_assertion_failure(const char* message);
#define FMT_ASSERT(condition, message) \
if (!(condition)) throw_assertion_failure(message);
#include "gtest/gtest.h"
class assertion_failure : public std::logic_error { class assertion_failure : public std::logic_error {
public: public:
@ -22,8 +26,11 @@ class assertion_failure : public std::logic_error {
void assertion_failure::avoid_weak_vtable() {} void assertion_failure::avoid_weak_vtable() {}
#define FMT_ASSERT(condition, message) \ // We use a separate function (rather than throw directly from FMT_ASSERT) to
if (!(condition)) throw assertion_failure(message); // avoid GCC's -Wterminate warning when FMT_ASSERT is used in a destructor.
inline void throw_assertion_failure(const char* message) {
throw assertion_failure(message);
}
// Expects an assertion failure. // Expects an assertion failure.
#define EXPECT_ASSERT(stmt, message) \ #define EXPECT_ASSERT(stmt, message) \

View File

@ -7,7 +7,7 @@
#include <cstdlib> #include <cstdlib>
#include "gtest.h" #include "gtest/gtest.h"
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>

View File

@ -0,0 +1,48 @@
// Formatting library for C++ - Unicode tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include <iomanip>
#include <locale>
#include <vector>
#include "fmt/chrono.h"
#include "gmock/gmock.h"
#include "util.h" // get_locale
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.1251");
if (loc == std::locale::classic()) return;
auto s = std::string();
try {
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());
return;
}
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 500
auto&& os = std::ostringstream();
os.imbue(loc);
auto tm = std::tm();
tm.tm_wday = 1;
os << std::put_time(&tm, "%a");
auto wd = os.str();
if (wd == "??") {
EXPECT_EQ(s, "День недели: ??");
fmt::print("std::locale gives ?? as a weekday.\n");
return;
}
#endif
EXPECT_THAT((std::vector<std::string>{"День недели: пн", "День недели: Пн"}),
Contains(s));
}

View File

@ -9,42 +9,38 @@
#include <cstring> #include <cstring>
void increment(char* s) { const char* const file_content = "Don't panic!";
for (int i = static_cast<int>(std::strlen(s)) - 1; i >= 0; --i) {
if (s[i] != '9') {
++s[i];
break;
}
s[i] = '0';
}
}
std::string get_system_error(int error_code) {
#if defined(__MINGW32__) || !defined(_WIN32)
return strerror(error_code);
#else
enum { BUFFER_SIZE = 200 };
char buffer[BUFFER_SIZE];
if (strerror_s(buffer, BUFFER_SIZE, error_code))
throw std::exception("strerror_s failed");
return buffer;
#endif
}
const char* const FILE_CONTENT = "Don't panic!";
fmt::buffered_file open_buffered_file(FILE** fp) { fmt::buffered_file open_buffered_file(FILE** fp) {
#if FMT_USE_FCNTL #if FMT_USE_FCNTL
fmt::file read_end, write_end; fmt::file read_end, write_end;
fmt::file::pipe(read_end, write_end); fmt::file::pipe(read_end, write_end);
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT)); write_end.write(file_content, std::strlen(file_content));
write_end.close(); write_end.close();
fmt::buffered_file f = read_end.fdopen("r"); fmt::buffered_file f = read_end.fdopen("r");
if (fp) *fp = f.get(); if (fp) *fp = f.get();
#else #else
fmt::buffered_file f("test-file", "w"); fmt::buffered_file f("test-file", "w");
fputs(FILE_CONTENT, f.get()); fputs(file_content, f.get());
if (fp) *fp = f.get(); if (fp) *fp = f.get();
#endif #endif
return f; return f;
} }
std::locale do_get_locale(const char* name) {
try {
return std::locale(name);
} catch (const std::runtime_error&) {
}
return std::locale::classic();
}
std::locale get_locale(const char* name, const char* alt_name) {
auto loc = do_get_locale(name);
if (loc == std::locale::classic() && alt_name) {
loc = do_get_locale(alt_name);
}
if (loc == std::locale::classic())
fmt::print(stderr, "{} locale is missing.\n", name);
return loc;
}

View File

@ -7,12 +7,11 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <locale>
#include <string> #include <string>
#include "fmt/os.h" #include "fmt/os.h"
enum { BUFFER_SIZE = 256 };
#ifdef _MSC_VER #ifdef _MSC_VER
# define FMT_VSNPRINTF vsprintf_s # define FMT_VSNPRINTF vsprintf_s
#else #else
@ -27,12 +26,7 @@ void safe_sprintf(char (&buffer)[SIZE], const char* format, ...) {
va_end(args); va_end(args);
} }
// Increment a number in a string. extern const char* const file_content;
void increment(char* s);
std::string get_system_error(int error_code);
extern const char* const FILE_CONTENT;
// Opens a buffered file for reading. // Opens a buffered file for reading.
fmt::buffered_file open_buffered_file(FILE** fp = nullptr); fmt::buffered_file open_buffered_file(FILE** fp = nullptr);
@ -48,37 +42,40 @@ inline FILE* safe_fopen(const char* filename, const char* mode) {
#endif #endif
} }
template <typename Char> class BasicTestString { template <typename Char> class basic_test_string {
private: private:
std::basic_string<Char> value_; std::basic_string<Char> value_;
static const Char EMPTY[]; static const Char empty[];
public: public:
explicit BasicTestString(const Char* value = EMPTY) : value_(value) {} explicit basic_test_string(const Char* value = empty) : value_(value) {}
const std::basic_string<Char>& value() const { return value_; } const std::basic_string<Char>& value() const { return value_; }
}; };
template <typename Char> const Char BasicTestString<Char>::EMPTY[] = {0}; template <typename Char> const Char basic_test_string<Char>::empty[] = {0};
typedef BasicTestString<char> TestString; typedef basic_test_string<char> test_string;
typedef BasicTestString<wchar_t> TestWString; typedef basic_test_string<wchar_t> test_wstring;
template <typename Char> template <typename Char>
std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os, std::basic_ostream<Char>& operator<<(std::basic_ostream<Char>& os,
const BasicTestString<Char>& s) { const basic_test_string<Char>& s) {
os << s.value(); os << s.value();
return os; return os;
} }
class Date { class date {
int year_, month_, day_; int year_, month_, day_;
public: public:
Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} date(int year, int month, int day) : year_(year), month_(month), day_(day) {}
int year() const { return year_; } int year() const { return year_; }
int month() const { return month_; } int month() const { return month_; }
int day() const { return day_; } int day() const { return day_; }
}; };
// Returns a locale with the given name if available or classic locale othewise.
std::locale get_locale(const char* name, const char* alt_name = nullptr);

View File

@ -0,0 +1,427 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/xchar.h"
#include <complex>
#include "fmt/chrono.h"
#include "fmt/color.h"
#include "fmt/ostream.h"
#include "fmt/ranges.h"
#include "gtest/gtest.h"
using fmt::detail::max_value;
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()};
}
struct non_string {};
} // namespace test_ns
template <typename T> class is_string_test : public testing::Test {};
using string_char_types = testing::Types<char, wchar_t, char16_t, char32_t>;
TYPED_TEST_SUITE(is_string_test, string_char_types);
template <typename Char>
struct derived_from_string_view : fmt::basic_string_view<Char> {};
TYPED_TEST(is_string_test, is_string) {
EXPECT_TRUE(fmt::detail::is_string<TypeParam*>::value);
EXPECT_TRUE(fmt::detail::is_string<const TypeParam*>::value);
EXPECT_TRUE(fmt::detail::is_string<TypeParam[2]>::value);
EXPECT_TRUE(fmt::detail::is_string<const TypeParam[2]>::value);
EXPECT_TRUE(fmt::detail::is_string<std::basic_string<TypeParam>>::value);
EXPECT_TRUE(fmt::detail::is_string<fmt::basic_string_view<TypeParam>>::value);
EXPECT_TRUE(
fmt::detail::is_string<derived_from_string_view<TypeParam>>::value);
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);
}
// std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1900
struct explicitly_convertible_to_wstring_view {
explicit operator fmt::wstring_view() const { return L"foo"; }
};
TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
EXPECT_EQ(L"foo",
fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
}
#endif
TEST(xchar_test, format) {
EXPECT_EQ(L"42", fmt::format(L"{}", 42));
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2));
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc"));
EXPECT_EQ(L"z", fmt::format(L"{}", L'z'));
EXPECT_THROW(fmt::format(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'));
EXPECT_EQ(L"Cyrillic letter \x42e",
fmt::format(L"Cyrillic letter {}", L'\x42e'));
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1));
}
TEST(xchar_test, compile_time_string) {
#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
#endif
}
#if __cplusplus > 201103L
struct custom_char {
int value;
custom_char() = default;
template <typename T>
constexpr custom_char(T val) : value(static_cast<int>(val)) {}
operator int() const { return value; }
};
int to_ascii(custom_char c) { return c; }
FMT_BEGIN_NAMESPACE
template <> struct is_char<custom_char> : std::true_type {};
FMT_END_NAMESPACE
TEST(xchar_test, format_custom_char) {
const custom_char format[] = {'{', '}', 0};
auto result = fmt::format(format, custom_char('x'));
EXPECT_EQ(result.size(), 1);
EXPECT_EQ(result[0], custom_char('x'));
}
#endif
// Convert a char8_t string to std::string. Otherwise GTest will insist on
// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423.
template <typename S> std::string from_u8str(const S& str) {
return std::string(str.begin(), str.end());
}
TEST(xchar_test, format_utf8_precision) {
using str_type = std::basic_string<fmt::detail::char8_type>;
auto format =
str_type(reinterpret_cast<const fmt::detail::char8_type*>(u8"{:.4}"));
auto str = str_type(reinterpret_cast<const fmt::detail::char8_type*>(
u8"caf\u00e9s")); // cafés
auto result = fmt::format(format, str);
EXPECT_EQ(fmt::detail::compute_width(result), 4);
EXPECT_EQ(result.size(), 5);
EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
}
TEST(xchar_test, format_to) {
auto buf = std::vector<wchar_t>();
fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0');
EXPECT_STREQ(buf.data(), L"42");
}
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 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);
EXPECT_EQ(L"42", w);
}
TEST(format_test, wide_format_to_n) {
wchar_t buffer[4];
buffer[3] = L'x';
auto result = fmt::format_to_n(buffer, 3, L"{}", 12345);
EXPECT_EQ(5u, result.size);
EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ(L"123x", fmt::wstring_view(buffer, 4));
buffer[0] = L'x';
buffer[1] = L'x';
buffer[2] = L'x';
result = fmt::format_to_n(buffer, 3, L"{}", L'A');
EXPECT_EQ(1u, result.size);
EXPECT_EQ(buffer + 1, result.out);
EXPECT_EQ(L"Axxx", fmt::wstring_view(buffer, 4));
result = fmt::format_to_n(buffer, 3, L"{}{} ", L'B', L'C');
EXPECT_EQ(3u, result.size);
EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
}
#if FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, format_udl) {
using namespace fmt::literals;
EXPECT_EQ(L"{}c{}"_format(L"ab", 1), fmt::format(L"{}c{}", L"ab", 1));
}
TEST(xchar_test, named_arg_udl) {
using namespace fmt::literals;
auto udl_a =
fmt::format(L"{first}{second}{first}{third}", L"first"_a = L"abra",
L"second"_a = L"cad", L"third"_a = 99);
EXPECT_EQ(
fmt::format(L"{first}{second}{first}{third}", fmt::arg(L"first", L"abra"),
fmt::arg(L"second", L"cad"), fmt::arg(L"third", 99)),
udl_a);
}
#endif // FMT_USE_USER_DEFINED_LITERALS
TEST(xchar_test, print) {
// Check that the wide print overload compiles.
if (fmt::detail::const_check(false)) fmt::print(L"test");
}
TEST(xchar_test, join) {
int v[3] = {1, 2, 3};
EXPECT_EQ(fmt::format(L"({})", fmt::join(v, v + 3, L", ")), L"(1, 2, 3)");
auto t = std::tuple<wchar_t, int, float>('a', 1, 2.0f);
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
}
enum streamable_enum {};
std::wostream& operator<<(std::wostream& os, streamable_enum) {
return os << L"streamable_enum";
}
enum unstreamable_enum {};
TEST(xchar_test, enum) {
EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum()));
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
}
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);
}
namespace fake_qt {
class QString {
public:
QString(const wchar_t* s) : s_(s) {}
const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); }
int size() const FMT_NOEXCEPT { return static_cast<int>(s_.size()); }
private:
std::wstring s_;
};
fmt::basic_string_view<wchar_t> to_string_view(const QString& s) FMT_NOEXCEPT {
return {s.utf16(), static_cast<size_t>(s.size())};
}
} // namespace fake_qt
TEST(format_test, format_foreign_strings) {
using fake_qt::QString;
EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42");
EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42");
}
TEST(xchar_test, chrono) {
auto tm = std::tm();
tm.tm_year = 116;
tm.tm_mon = 3;
tm.tm_mday = 25;
tm.tm_hour = 11;
tm.tm_min = 22;
tm.tm_sec = 33;
EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm),
"The date is 2016-04-25 11:22:33.");
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
}
TEST(xchar_test, color) {
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
}
TEST(xchar_test, ostream) {
std::wostringstream wos;
fmt::print(wos, L"Don't {}!", L"panic");
EXPECT_EQ(L"Don't panic!", wos.str());
}
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 '?'; }
std::string do_grouping() const override { return "\03"; }
Char do_thousands_sep() const override { return '~'; }
};
template <typename Char> struct no_grouping : std::numpunct<Char> {
protected:
Char do_decimal_point() const override { return '.'; }
std::string do_grouping() const override { return ""; }
Char do_thousands_sep() const override { return ','; }
};
template <typename Char> struct special_grouping : std::numpunct<Char> {
protected:
Char do_decimal_point() const override { return '.'; }
std::string do_grouping() const override { return "\03\02"; }
Char do_thousands_sep() const override { return ','; }
};
template <typename Char> struct small_grouping : std::numpunct<Char> {
protected:
Char do_decimal_point() const override { return '.'; }
std::string do_grouping() const override { return "\01"; }
Char do_thousands_sep() const override { return ','; }
};
TEST(locale_test, double_decimal_point) {
auto loc = std::locale(std::locale(), new numpunct<char>());
EXPECT_EQ("1?23", fmt::format(loc, "{:L}", 1.23));
EXPECT_EQ("1?230000", fmt::format(loc, "{:Lf}", 1.23));
}
TEST(locale_test, format) {
auto loc = std::locale(std::locale(), new numpunct<char>());
EXPECT_EQ("1234567", fmt::format(std::locale(), "{:L}", 1234567));
EXPECT_EQ("1~234~567", fmt::format(loc, "{:L}", 1234567));
EXPECT_EQ("-1~234~567", fmt::format(loc, "{:L}", -1234567));
EXPECT_EQ("-256", fmt::format(loc, "{:L}", -256));
fmt::format_arg_store<fmt::format_context, int> as{1234567};
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:L}", fmt::format_args(as)));
auto s = std::string();
fmt::format_to(std::back_inserter(s), loc, "{:L}", 1234567);
EXPECT_EQ("1~234~567", s);
auto no_grouping_loc = std::locale(std::locale(), new no_grouping<char>());
EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:L}", 1234567));
auto special_grouping_loc =
std::locale(std::locale(), new special_grouping<char>());
EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:L}", 12345678));
EXPECT_EQ("12,345", fmt::format(special_grouping_loc, "{:L}", 12345));
auto small_grouping_loc =
std::locale(std::locale(), new small_grouping<char>());
EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
fmt::format(small_grouping_loc, "{:L}", max_value<uint32_t>()));
}
TEST(locale_test, format_detault_align) {
auto loc = std::locale({}, new special_grouping<char>());
EXPECT_EQ(" 12,345", fmt::format(loc, "{:8L}", 12345));
}
TEST(locale_test, format_plus) {
auto loc = std::locale({}, new special_grouping<char>());
EXPECT_EQ("+100", fmt::format(loc, "{:+L}", 100));
}
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};
EXPECT_EQ(L"1~234~567",
fmt::vformat(loc, L"{:L}", fmt::basic_format_args<wcontext>(as)));
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>());
EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:L}", 1234567));
auto special_grouping_loc =
std::locale(std::locale(), new special_grouping<wchar_t>());
EXPECT_EQ(L"1,23,45,678",
fmt::format(special_grouping_loc, L"{:L}", 12345678));
auto small_grouping_loc =
std::locale(std::locale(), new small_grouping<wchar_t>());
EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
fmt::format(small_grouping_loc, L"{:L}", max_value<uint32_t>()));
}
TEST(locale_test, double_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);
}
FMT_BEGIN_NAMESPACE
template <class charT> struct formatter<std::complex<double>, charT> {
private:
detail::dynamic_format_specs<char> specs_;
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;
}
template <class FormatContext>
typename FormatContext::iterator format(const std::complex<double>& c,
FormatContext& ctx) {
detail::handle_dynamic_spec<detail::precision_checker>(
specs_.precision, specs_.precision_ref, ctx);
auto specs = std::string();
if (specs_.precision > 0) specs = fmt::format(".{}", specs_.precision);
if (specs_.type) specs += specs_.type;
auto real = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + specs + "}"), c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + specs + "}"), c.imag());
auto fill_align_width = std::string();
if (specs_.width > 0) fill_align_width = fmt::format(">{}", specs_.width);
return format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
: fmt::format("{}i", imag));
}
};
FMT_END_NAMESPACE
TEST(locale_test, complex) {
std::string s = fmt::format("{}", std::complex<double>(1, 2));
EXPECT_EQ(s, "(1+2i)");
EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR

View File

@ -384,5 +384,4 @@ endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Windows") if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0) target_compile_definitions(dynarmic PRIVATE FMT_USE_WINDOWS_H=0)
endif() endif()
# Disable this as it relies on a non-standard feature target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
target_compile_definitions(dynarmic PRIVATE FMT_USE_USER_DEFINED_LITERALS=0)

View File

@ -5,6 +5,8 @@
#include "dynarmic/common/fp/unpacked.h" #include "dynarmic/common/fp/unpacked.h"
#include <algorithm>
#include "dynarmic/common/fp/fpsr.h" #include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/fp/info.h" #include "dynarmic/common/fp/info.h"
#include "dynarmic/common/fp/mantissa_util.h" #include "dynarmic/common/fp/mantissa_util.h"

View File

@ -19,7 +19,7 @@ namespace detail {
template<size_t N> template<size_t N>
inline constexpr std::array<char, N> StringToArray(const char (&str)[N + 1]) { inline constexpr std::array<char, N> StringToArray(const char (&str)[N + 1]) {
std::array<char, N> result; std::array<char, N> result{};
for (size_t i = 0; i < N; i++) { for (size_t i = 0; i < N; i++) {
result[i] = str[i]; result[i] = str[i];
} }

View File

@ -208,10 +208,10 @@ static void RunTestInstance(Dynarmic::A64::Jit& jit, A64Unicorn& uni, A64TestEnv
fmt::print("Initial register listing:\n"); fmt::print("Initial register listing:\n");
for (size_t i = 0; i < regs.size(); ++i) { for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:016x}\n", static_cast<A64::Reg>(i), regs[i]); fmt::print("{:3s}: {:016x}\n", A64::RegToString(static_cast<A64::Reg>(i)), regs[i]);
} }
for (size_t i = 0; i < vecs.size(); ++i) { for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {}\n", static_cast<A64::Vec>(i), vecs[i]); fmt::print("{:3s}: {}{}\n", A64::VecToString(static_cast<A64::Vec>(i)), vecs[i][1], vecs[i][0]);
} }
fmt::print("sp : {:016x}\n", initial_sp); fmt::print("sp : {:016x}\n", initial_sp);
fmt::print("pc : {:016x}\n", instructions_start); fmt::print("pc : {:016x}\n", instructions_start);
@ -228,11 +228,14 @@ static void RunTestInstance(Dynarmic::A64::Jit& jit, A64Unicorn& uni, A64TestEnv
fmt::print(" unicorn dynarmic\n"); fmt::print(" unicorn dynarmic\n");
const auto uni_regs = uni.GetRegisters(); const auto uni_regs = uni.GetRegisters();
for (size_t i = 0; i < regs.size(); ++i) { for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:016x} {:016x} {}\n", static_cast<A64::Reg>(i), uni_regs[i], jit.GetRegisters()[i], uni_regs[i] != jit.GetRegisters()[i] ? "*" : ""); fmt::print("{:3s}: {:016x} {:016x} {}\n", A64::RegToString(static_cast<A64::Reg>(i)), uni_regs[i], jit.GetRegisters()[i], uni_regs[i] != jit.GetRegisters()[i] ? "*" : "");
} }
const auto uni_vecs = uni.GetVectors(); const auto uni_vecs = uni.GetVectors();
for (size_t i = 0; i < vecs.size(); ++i) { for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {} {} {}\n", static_cast<A64::Vec>(i), uni_vecs[i], jit.GetVectors()[i], uni_vecs[i] != jit.GetVectors()[i] ? "*" : ""); fmt::print("{:3s}: {}{} {}{} {}\n", A64::VecToString(static_cast<A64::Vec>(i)),
uni_vecs[i][1], uni_vecs[i][0],
jit.GetVectors()[i][1], jit.GetVectors()[i][0],
uni_vecs[i] != jit.GetVectors()[i] ? "*" : "");
} }
fmt::print("sp : {:016x} {:016x} {}\n", uni.GetSP(), jit.GetSP(), uni.GetSP() != jit.GetSP() ? "*" : ""); fmt::print("sp : {:016x} {:016x} {}\n", uni.GetSP(), jit.GetSP(), uni.GetSP() != jit.GetSP() ? "*" : "");
fmt::print("pc : {:016x} {:016x} {}\n", uni.GetPC(), jit.GetPC(), uni.GetPC() != jit.GetPC() ? "*" : ""); fmt::print("pc : {:016x} {:016x} {}\n", uni.GetPC(), jit.GetPC(), uni.GetPC() != jit.GetPC() ? "*" : "");

View File

@ -54,11 +54,11 @@ create_target_directory_groups(dynarmic_print_info)
target_link_libraries(dynarmic_tests PRIVATE dynarmic boost catch fmt mp xbyak) target_link_libraries(dynarmic_tests PRIVATE dynarmic boost catch fmt mp xbyak)
target_include_directories(dynarmic_tests PRIVATE . ../src) target_include_directories(dynarmic_tests PRIVATE . ../src)
target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS}) target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=0 CATCH_CONFIG_ENABLE_BENCHMARKING=1) target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=1 CATCH_CONFIG_ENABLE_BENCHMARKING=1)
target_link_libraries(dynarmic_print_info PRIVATE dynarmic boost catch fmt mp) target_link_libraries(dynarmic_print_info PRIVATE dynarmic boost catch fmt mp)
target_include_directories(dynarmic_print_info PRIVATE . ../src) target_include_directories(dynarmic_print_info PRIVATE . ../src)
target_compile_options(dynarmic_print_info PRIVATE ${DYNARMIC_CXX_FLAGS}) target_compile_options(dynarmic_print_info PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_print_info PRIVATE FMT_USE_USER_DEFINED_LITERALS=0) target_compile_definitions(dynarmic_print_info PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
add_test(dynarmic_tests dynarmic_tests --durations yes) add_test(dynarmic_tests dynarmic_tests --durations yes)

View File

@ -53,8 +53,9 @@ template <typename ContiguousContainer>
std::string out; std::string out;
out.reserve(std::size(data) * pad_width); out.reserve(std::size(data) * pad_width);
const auto format_str = fmt::runtime(upper ? "{:02X}" : "{:02x}");
for (const u8 c : data) { for (const u8 c : data) {
out += fmt::format(upper ? "{:02X}" : "{:02x}", c); out += fmt::format(format_str, c);
} }
return out; return out;

Some files were not shown because too many files have changed in this diff Show More