mirror of
https://github.com/fmtlib/fmt.git
synced 2025-06-25 09:21:41 +02:00
Compare commits
397 Commits
Author | SHA1 | Date | |
---|---|---|---|
9bdd1596ce | |||
d151562bdd | |||
346500e70b | |||
a434a8f778 | |||
9eb47d951a | |||
51c58a56ba | |||
3fc33f6273 | |||
2e32db5b99 | |||
c1ce6e01f7 | |||
1c3c80dc1f | |||
d1d653d895 | |||
73c8437485 | |||
e588b02b17 | |||
1a62711d01 | |||
5b02881582 | |||
a133187a8c | |||
80ce222ca6 | |||
770a94edef | |||
2864e8432a | |||
21a295c272 | |||
96c68afe69 | |||
664dd88e31 | |||
69779b4ed6 | |||
01a172c969 | |||
08ca40ea91 | |||
dd97f4920c | |||
2951169481 | |||
d3e668418f | |||
52d0e1bbe3 | |||
5d32ccfc31 | |||
3cf619de55 | |||
2559983e7a | |||
026f99178e | |||
9f70fc3e7a | |||
6012dc9ab4 | |||
85050aa2e6 | |||
ff486a72a7 | |||
678341275b | |||
6f01b6ebb6 | |||
61c5a51604 | |||
02bfd8a9a5 | |||
3c24052cf1 | |||
f72a905eb3 | |||
941d5e147a | |||
ee2b828b9a | |||
5bb8856655 | |||
1c0c59d4a0 | |||
b1adaa9881 | |||
48e8d0ebef | |||
ce00979152 | |||
db4a6cfbf9 | |||
29a1ea795a | |||
8a06ca84c7 | |||
153f753bde | |||
eafd079868 | |||
0c6919ec72 | |||
197a5c3721 | |||
68742e1d87 | |||
1e8493196e | |||
58e6c84f5a | |||
75a4525e5f | |||
6ccb2e241b | |||
bed134a4aa | |||
b2d3a86ec0 | |||
13d82e32bd | |||
2161a73f2b | |||
e00997b004 | |||
0415cf2350 | |||
3bafd0749b | |||
f733882b55 | |||
dc22360c34 | |||
1f1b50707c | |||
24924128e3 | |||
c54cd71800 | |||
43e9b29e50 | |||
b55ea58705 | |||
4098970db2 | |||
314e15001f | |||
f499b393d1 | |||
6c30f41443 | |||
1acb73f970 | |||
09a13244c8 | |||
419db8baa1 | |||
9fc4161f5e | |||
25d6916b3a | |||
0b2eb6501c | |||
fd1cabe464 | |||
a844d7ab81 | |||
47d3968092 | |||
7800173eb1 | |||
b4218aa0f8 | |||
8a3a8177d6 | |||
e5f2f8ce7a | |||
75765bfad5 | |||
9bd9738da0 | |||
bd5f903f28 | |||
06e437fd98 | |||
1bd4f54fa6 | |||
11cc2903e4 | |||
b124e3e8e7 | |||
ffd5f3469f | |||
0f0e5ddf5f | |||
1f110702a1 | |||
4ccbe4b5f2 | |||
40638a75b3 | |||
c8dd9cc99d | |||
4bbe57cebf | |||
55b6130055 | |||
ae3ea156ea | |||
77165fdf85 | |||
65ac626c58 | |||
cd0b3f9695 | |||
cef1e4354b | |||
0201c8db21 | |||
9e3f3e8cff | |||
aa07c57654 | |||
a73d89e9c7 | |||
cb8e7caf7c | |||
b3fd0005dd | |||
7b478f9dec | |||
c85efef312 | |||
455a7c0787 | |||
674c326d7c | |||
061a9897fe | |||
d2d1c9c560 | |||
b6e19e5953 | |||
f219dcd59b | |||
dea7fde8b7 | |||
5390e29d42 | |||
9f6434dcde | |||
dac9a7f99d | |||
3ca9533f38 | |||
7eec036d9a | |||
e6b37b4aff | |||
8cf4c52068 | |||
74532c23a3 | |||
b308159be5 | |||
162995fedd | |||
8b41362a0a | |||
1b1c70108a | |||
d7e72a09e0 | |||
2201890d7a | |||
6100ed4bb3 | |||
1afe201ae8 | |||
cd2b99032f | |||
9acf89fef6 | |||
9ea42fb26e | |||
da2569827e | |||
35959a31d7 | |||
ec2463c905 | |||
0012917f69 | |||
9e450911fa | |||
068d20bc31 | |||
a99fbe67b9 | |||
adbed11ed4 | |||
8ab1c5c6e8 | |||
a770009fcc | |||
598e6042d1 | |||
e09814dc93 | |||
b272fb3605 | |||
f94b7364b9 | |||
7abec071b5 | |||
b7eb8c8921 | |||
ae7c50185d | |||
9f2e7edaeb | |||
fd52de0c6b | |||
f675cb887e | |||
73a16b827f | |||
72879db40e | |||
d3aa0c3a28 | |||
31de9a1b80 | |||
227bfe62dd | |||
95dfdc6cc4 | |||
5916ff63c4 | |||
1ab80aa92c | |||
4f4d876616 | |||
f443bd3baf | |||
1219b65f21 | |||
071794ec65 | |||
d22e4ad85b | |||
983806b0c1 | |||
02af5beb8a | |||
123e7f7fc3 | |||
168460f02c | |||
a64f60c849 | |||
1a599117d8 | |||
b160123e39 | |||
5981588565 | |||
8bbe76af3a | |||
4ca6821e8f | |||
7111a1eb9f | |||
ae00bbdc91 | |||
e71e07d9fb | |||
0184df7020 | |||
1cbae6e9ba | |||
159f89e2b8 | |||
4b120b68ae | |||
186b225d9d | |||
4cbf4888ea | |||
e31f2b3d03 | |||
62da1db62a | |||
3bc28fcc6b | |||
3c05fa46c6 | |||
ba6e330fd3 | |||
6037b3cae9 | |||
fafb03fa6d | |||
2f9acd1838 | |||
aaf829bfb1 | |||
b994a0ab13 | |||
bb205d940d | |||
ef7369ce90 | |||
40e4c227db | |||
ea54b21e78 | |||
9cbf4b087c | |||
1200a34e10 | |||
9c7e2a6c6f | |||
34e921f6fe | |||
c3be0f593d | |||
c68703c9f4 | |||
9a21728b0a | |||
3de36e9348 | |||
4afb39bc24 | |||
7ffa62db18 | |||
0d07db1234 | |||
d19ed6716d | |||
99b6e928d4 | |||
57cd3f72e9 | |||
111fc127fe | |||
6003ec3f25 | |||
8877a67724 | |||
75fff1db64 | |||
28d7191c27 | |||
43271ba8e8 | |||
63a9f3fcd4 | |||
4cf59ce734 | |||
7395472dde | |||
9108b25da9 | |||
4d366c68b7 | |||
ded1e7679e | |||
c7edd8e570 | |||
75108a56f6 | |||
3e1f70fe02 | |||
125fc5e520 | |||
6793ffc1d6 | |||
f4fcc5fd28 | |||
4de41aa655 | |||
404a880bd4 | |||
092d2dc7b2 | |||
093e554211 | |||
d0696b0aa6 | |||
66d7746bb3 | |||
e9bff78814 | |||
57b6f2966d | |||
d79493e5ee | |||
78842ce0d6 | |||
5420bcce2d | |||
56a2e2075c | |||
ed117baa4f | |||
f26446290b | |||
7e1cb3237a | |||
f67783d7e6 | |||
1c6d85f7bb | |||
4a1da44f91 | |||
080b6899d2 | |||
c01ec54fde | |||
b0c2ab93fa | |||
9b7fe2a4a1 | |||
c58b7d9c2f | |||
ceff9b0b2e | |||
3dc8639f8a | |||
dcde089b4e | |||
2145a7bdcc | |||
52ae134f84 | |||
0d6dd0cc6a | |||
1f918159ed | |||
6868f888b2 | |||
87cd545a1e | |||
12f9437e22 | |||
bb0c8bfea8 | |||
e6e8298904 | |||
0f0848e4f4 | |||
a1fb5c7337 | |||
8a411c2bca | |||
0047dc10a2 | |||
263cdef8a0 | |||
d4ca54253a | |||
5bb7b28e15 | |||
1409dfe76b | |||
f1559e1d56 | |||
ffd05e65ed | |||
0889856d61 | |||
d6eede9e08 | |||
213e09644f | |||
6bfc9af8c9 | |||
3487f1b9cd | |||
791294d17b | |||
8e700619b7 | |||
58c6f8c7f5 | |||
40414b3446 | |||
b7a157401e | |||
7aa58c30bf | |||
8e9bffa986 | |||
ce4d87acd4 | |||
21acc2af43 | |||
00669427df | |||
d39ebf3ff2 | |||
6498bc6d31 | |||
a967dcbe20 | |||
8498bc97dd | |||
e2ea940673 | |||
2bc5585ff0 | |||
bb728a572a | |||
36d1390e67 | |||
599e0aef45 | |||
91f7619cc9 | |||
c4dc6bef24 | |||
646966e973 | |||
a5abe5d95c | |||
1cbc5fa6cb | |||
f7a5748fd3 | |||
0e94b931a2 | |||
5e58eb97b1 | |||
3a15ea3ea5 | |||
b87ac4d840 | |||
a927dda9bb | |||
dd11d45847 | |||
b55551f900 | |||
96f91428c6 | |||
b732f28c00 | |||
a82c1dc6d9 | |||
2730e90186 | |||
e4d6d9d7c8 | |||
a1079e9fd6 | |||
b66bb6b71f | |||
b60114533f | |||
c41cea8b18 | |||
0c7650373c | |||
0571013709 | |||
d2c9276fcd | |||
0fc7bd1573 | |||
b4f1988c4b | |||
4b8f8fac96 | |||
3b2fc033d1 | |||
ac59d9f3a4 | |||
8f27ce4d8b | |||
89b0c71fa9 | |||
f6a783ad2e | |||
ccc8f5db02 | |||
20fdb88a1c | |||
f29901097f | |||
758446c80d | |||
f7aedc5fc4 | |||
840a817ed2 | |||
79c923ba2c | |||
5dc577c064 | |||
c6d1a94a9f | |||
0656045d02 | |||
c85ae23c73 | |||
b3bf665764 | |||
0887887e23 | |||
ac71d853be | |||
6649b8e0ca | |||
56b5c192a0 | |||
b2f0b6e44e | |||
3d9f3c163b | |||
19547d5148 | |||
972ffd3151 | |||
2ed412fa38 | |||
df4dcf2ece | |||
f7a4b4ab91 | |||
611cf0b3c6 | |||
58a8f2f539 | |||
1882b9687b | |||
6de0454b42 | |||
16e3c48bb0 | |||
8ce5f680f2 | |||
2fd8f9ec8a | |||
fe642d7648 | |||
a128b5b2cb | |||
466128de00 | |||
22e98a5b6a | |||
f18a3f36a7 | |||
7cad33563c | |||
e1ab6bc006 | |||
24a88545d9 | |||
422e7b9d70 | |||
d1dd9d5327 | |||
9a56a608ee | |||
c76957565c | |||
200ee6f108 | |||
bcd9b9331a | |||
345ba07f1d | |||
9e2490be4c | |||
3f75e2b69e | |||
744302add0 | |||
f5556225a4 | |||
ad3c7855e2 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
*.iml
|
||||
.idea/
|
||||
|
39
.travis.yml
39
.travis.yml
@ -92,45 +92,6 @@ matrix:
|
||||
# g++ 4.8 on Linux with C++11
|
||||
- env: COMPILER=g++-4.8 BUILD=Debug STANDARD=11
|
||||
compiler: gcc
|
||||
- name: Android NDK (Gradle)
|
||||
language: android
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- ninja-build
|
||||
- curl
|
||||
- tree
|
||||
android:
|
||||
components:
|
||||
- tools
|
||||
- platform-tools
|
||||
- android-25 # 7.0
|
||||
- android-27 # 8.1
|
||||
- android-28 # 9.0
|
||||
- build-tools-28.0.3
|
||||
before_install:
|
||||
# Install Gradle from https://sdkman.io/
|
||||
- curl -s "https://get.sdkman.io" | bash > /dev/null
|
||||
- source "$HOME/.sdkman/bin/sdkman-init.sh"
|
||||
- sdk version
|
||||
- sdk install gradle
|
||||
- sdk use gradle
|
||||
- gradle --version
|
||||
install:
|
||||
# Accept SDK Licenses + Install NDK
|
||||
- yes | sdkmanager --update > /dev/null 2>&1
|
||||
- sdkmanager ndk-bundle > /dev/null 2>&1
|
||||
before_script:
|
||||
- pushd ./support
|
||||
script:
|
||||
- gradle clean
|
||||
- gradle assemble
|
||||
after_success:
|
||||
- popd;
|
||||
- tree ./libs
|
||||
|
||||
before_script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then export CXX=${COMPILER}; fi
|
||||
|
@ -24,13 +24,24 @@ function(join result_var)
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Sets a cache variable with a docstring joined from multiple arguments:
|
||||
# set(<variable> <value>... CACHE <type> <docstring>...)
|
||||
# This allows splitting a long docstring for readability.
|
||||
function(set_verbose)
|
||||
cmake_parse_arguments(SET_VERBOSE "" "" "CACHE" ${ARGN})
|
||||
list(GET SET_VERBOSE_CACHE 0 type)
|
||||
list(REMOVE_AT SET_VERBOSE_CACHE 0)
|
||||
join(doc ${SET_VERBOSE_CACHE})
|
||||
set(${SET_VERBOSE_UNPARSED_ARGUMENTS} CACHE ${type} ${doc})
|
||||
endfunction()
|
||||
|
||||
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||
# This should be done before the project command since the latter can set
|
||||
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||
if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||
join(doc "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING ${doc})
|
||||
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
endif ()
|
||||
|
||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||
@ -42,6 +53,7 @@ option(FMT_DOC "Generate the doc target." ${MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
|
||||
project(FMT CXX)
|
||||
|
||||
@ -60,7 +72,9 @@ message(STATUS "Version: ${FMT_VERSION}")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
@ -68,7 +82,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
set(FMT_REQUIRED_FEATURES cxx_auto_type cxx_variadic_templates)
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||
# compatibility with older CMake versions.
|
||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||
@ -78,7 +98,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
-Wcast-align -Wnon-virtual-dtor
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion
|
||||
-Wconversion -Wswitch-enum
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
|
||||
@ -98,7 +118,7 @@ endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
|
||||
-Wno-sign-conversion)
|
||||
-Wno-sign-conversion -Wdeprecated -Wweak-vtables)
|
||||
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||
if (HAS_NULLPTR_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
@ -122,7 +142,9 @@ if (MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"")
|
||||
endif ()
|
||||
# Set FrameworkPathOverride to get rid of MSB3644 warnings.
|
||||
set(netfxpath "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.0")
|
||||
join(netfxpath
|
||||
"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\"
|
||||
".NETFramework\\v4.0")
|
||||
file(WRITE run-msbuild.bat "
|
||||
${MSBUILD_SETUP}
|
||||
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||
@ -135,10 +157,8 @@ endif ()
|
||||
|
||||
include(CheckSymbolExists)
|
||||
if (WIN32)
|
||||
check_symbol_exists(open io.h HAVE_OPEN)
|
||||
check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
else ()
|
||||
check_symbol_exists(open fcntl.h HAVE_OPEN)
|
||||
check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
endif ()
|
||||
|
||||
@ -152,13 +172,8 @@ endfunction()
|
||||
|
||||
# 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
|
||||
locale.h ostream.h printf.h ranges.h
|
||||
safe-duration-cast.h)
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
if (HAVE_OPEN)
|
||||
add_headers(FMT_HEADERS posix.h)
|
||||
set(FMT_SOURCES ${FMT_SOURCES} src/posix.cc)
|
||||
endif ()
|
||||
locale.h os.h ostream.h posix.h printf.h ranges.h)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
@ -180,12 +195,21 @@ target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>)
|
||||
|
||||
set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX d)
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||
# property because it's not set by default.
|
||||
set(FMT_LIB_NAME fmt)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
@ -200,7 +224,6 @@ add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
@ -211,8 +234,9 @@ target_include_directories(fmt-header-only INTERFACE
|
||||
if (FMT_INSTALL)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
set(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||
@ -223,14 +247,17 @@ if (FMT_INSTALL)
|
||||
set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only)
|
||||
endif ()
|
||||
|
||||
set(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
|
||||
"Installation directory for include files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING
|
||||
"Installation directory for include files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
set(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, relative to ${CMAKE_INSTALL_PREFIX}.")
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, relative to "
|
||||
"${CMAKE_INSTALL_PREFIX}.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
@ -259,7 +286,9 @@ if (FMT_INSTALL)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
DESTINATION ${FMT_LIB_DIR})
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||
@ -285,7 +314,7 @@ set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||
if (MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
# Get the list of ignored files from .gitignore.
|
||||
file (STRINGS ${gitignore} lines)
|
||||
LIST(REMOVE_ITEM lines /doc/html)
|
||||
list(REMOVE_ITEM lines /doc/html)
|
||||
foreach (line ${lines})
|
||||
string(REPLACE "." "[.]" line "${line}")
|
||||
string(REPLACE "*" ".*" line "${line}")
|
||||
|
498
ChangeLog.rst
498
ChangeLog.rst
@ -1,3 +1,498 @@
|
||||
6.2.0 - 2020-04-05
|
||||
------------------
|
||||
|
||||
* Improved error reporting when trying to format an object of a non-formattable
|
||||
type:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::format("{}", S());
|
||||
|
||||
now gives::
|
||||
|
||||
include/fmt/core.h:1015:5: error: static_assert failed due to requirement
|
||||
'formattable' "Cannot format argument. To make type T formattable provide a
|
||||
formatter<T> specialization:
|
||||
https://fmt.dev/latest/api.html#formatting-user-defined-types"
|
||||
static_assert(
|
||||
^
|
||||
...
|
||||
note: in instantiation of function template specialization
|
||||
'fmt::v6::format<char [3], S, char>' requested here
|
||||
fmt::format("{}", S());
|
||||
^
|
||||
|
||||
if ``S`` is not formattable.
|
||||
|
||||
* Reduced library size by ~10%.
|
||||
|
||||
* Always print decimal point if ``#`` is specified
|
||||
(`#1476 <https://github.com/fmtlib/fmt/issues/1476>`_,
|
||||
`#1498 <https://github.com/fmtlib/fmt/issues/1498>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("{:#.0f}", 42.0);
|
||||
|
||||
now prints ``42.``
|
||||
|
||||
* Implemented the ``'L'`` specifier for locale-specific numeric formatting to
|
||||
improve compatibility with ``std::format``. The ``'n'`` specifier is now
|
||||
deprecated and will be removed in the next major release.
|
||||
|
||||
* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to
|
||||
``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically
|
||||
include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be
|
||||
disabled in the next major release.
|
||||
|
||||
* Added precision overflow detection in floating-point formatting.
|
||||
|
||||
* Implemented detection of invalid use of ``fmt::arg``.
|
||||
|
||||
* Used ``type_identity`` to block unnecessary template argument deduction.
|
||||
Thanks Tim Song.
|
||||
|
||||
* Improved UTF-8 handling
|
||||
(`#1109 <https://github.com/fmtlib/fmt/issues/1109>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("┌{0:─^{2}}┐\n"
|
||||
"│{1: ^{2}}│\n"
|
||||
"└{0:─^{2}}┘\n", "", "Привет, мир!", 20);
|
||||
|
||||
now prints::
|
||||
|
||||
┌────────────────────┐
|
||||
│ Привет, мир! │
|
||||
└────────────────────┘
|
||||
|
||||
on systems that support Unicode.
|
||||
|
||||
* Added experimental dynamic argument storage
|
||||
(`#1170 <https://github.com/fmtlib/fmt/issues/1170>`_,
|
||||
`#1584 <https://github.com/fmtlib/fmt/pull/1584>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back("answer");
|
||||
store.push_back(42);
|
||||
fmt::vprint("The {} is {}.\n", store);
|
||||
|
||||
prints::
|
||||
|
||||
The answer is 42.
|
||||
|
||||
Thanks `@vsolontsov-ll (Vladimir Solontsov)
|
||||
<https://github.com/vsolontsov-ll>`_.
|
||||
|
||||
* Made ``fmt::join`` accept ``initializer_list``
|
||||
(`#1591 <https://github.com/fmtlib/fmt/pull/1591>`_).
|
||||
Thanks `@Rapotkinnik (Nikolay Rapotkin) <https://github.com/Rapotkinnik>`_.
|
||||
|
||||
* Fixed handling of empty tuples
|
||||
(`#1588 <https://github.com/fmtlib/fmt/issues/1588>`_).
|
||||
|
||||
* Fixed handling of output iterators in ``format_to_n``
|
||||
(`#1506 <https://github.com/fmtlib/fmt/issues/1506>`_).
|
||||
|
||||
* Fixed formatting of ``std::chrono::duration`` types to wide output
|
||||
(`#1533 <https://github.com/fmtlib/fmt/pull/1533>`_).
|
||||
Thanks `@zeffy (pilao) <https://github.com/zeffy>`_.
|
||||
|
||||
* Added const ``begin`` and ``end`` overload to buffers
|
||||
(`#1553 <https://github.com/fmtlib/fmt/pull/1553>`_).
|
||||
Thanks `@dominicpoeschko <https://github.com/dominicpoeschko>`_.
|
||||
|
||||
* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``,
|
||||
``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely
|
||||
memory-constrained embedded system
|
||||
(`#1590 <https://github.com/fmtlib/fmt/pull/1590>`_).
|
||||
Thanks `@albaguirre (Alberto Aguirre) <https://github.com/albaguirre>`_.
|
||||
|
||||
* Made ``FMT_STRING`` work with ``constexpr`` ``string_view``
|
||||
(`#1589 <https://github.com/fmtlib/fmt/pull/1589>`_).
|
||||
Thanks `@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
|
||||
|
||||
* Implemented a minor optimization in the format string parser
|
||||
(`#1560 <https://github.com/fmtlib/fmt/pull/1560>`_).
|
||||
Thanks `@IkarusDeveloper <https://github.com/IkarusDeveloper>`_.
|
||||
|
||||
* Improved attribute detection
|
||||
(`#1469 <https://github.com/fmtlib/fmt/pull/1469>`_,
|
||||
`#1475 <https://github.com/fmtlib/fmt/pull/1475>`_,
|
||||
`#1576 <https://github.com/fmtlib/fmt/pull/1576>`_).
|
||||
Thanks `@federico-busato (Federico) <https://github.com/federico-busato>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@refnum <https://github.com/refnum>`_.
|
||||
|
||||
* Improved documentation
|
||||
(`#1481 <https://github.com/fmtlib/fmt/pull/1481>`_,
|
||||
`#1523 <https://github.com/fmtlib/fmt/pull/1523>`_).
|
||||
Thanks `@JackBoosY (Jack·Boos·Yu) <https://github.com/JackBoosY>`_,
|
||||
`@imba-tjd (谭九鼎) <https://github.com/imba-tjd>`_.
|
||||
|
||||
* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden``
|
||||
(`#1535 <https://github.com/fmtlib/fmt/pull/1535>`_).
|
||||
Thanks `@milianw (Milian Wolff) <https://github.com/milianw>`_.
|
||||
|
||||
* Implemented various build configuration fixes and improvements
|
||||
(`#1264 <https://github.com/fmtlib/fmt/issues/1264>`_,
|
||||
`#1460 <https://github.com/fmtlib/fmt/issues/1460>`_,
|
||||
`#1534 <https://github.com/fmtlib/fmt/pull/1534>`_,
|
||||
`#1536 <https://github.com/fmtlib/fmt/issues/1536>`_,
|
||||
`#1545 <https://github.com/fmtlib/fmt/issues/1545>`_,
|
||||
`#1546 <https://github.com/fmtlib/fmt/pull/1546>`_,
|
||||
`#1566 <https://github.com/fmtlib/fmt/issues/1566>`_,
|
||||
`#1582 <https://github.com/fmtlib/fmt/pull/1582>`_,
|
||||
`#1597 <https://github.com/fmtlib/fmt/issues/1597>`_,
|
||||
`#1598 <https://github.com/fmtlib/fmt/pull/1598>`_).
|
||||
Thanks `@ambitslix (Attila M. Szilagyi) <https://github.com/ambitslix>`_,
|
||||
`@jwillikers (Jordan Williams) <https://github.com/jwillikers>`_,
|
||||
`@stac47 (Laurent Stacul) <https://github.com/stac47>`_.
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#1433 <https://github.com/fmtlib/fmt/pull/1433>`_,
|
||||
`#1461 <https://github.com/fmtlib/fmt/issues/1461>`_,
|
||||
`#1470 <https://github.com/fmtlib/fmt/pull/1470>`_,
|
||||
`#1480 <https://github.com/fmtlib/fmt/pull/1480>`_,
|
||||
`#1485 <https://github.com/fmtlib/fmt/pull/1485>`_,
|
||||
`#1492 <https://github.com/fmtlib/fmt/pull/1492>`_,
|
||||
`#1493 <https://github.com/fmtlib/fmt/issues/1493>`_,
|
||||
`#1504 <https://github.com/fmtlib/fmt/issues/1504>`_,
|
||||
`#1505 <https://github.com/fmtlib/fmt/pull/1505>`_,
|
||||
`#1512 <https://github.com/fmtlib/fmt/pull/1512>`_,
|
||||
`#1515 <https://github.com/fmtlib/fmt/issues/1515>`_,
|
||||
`#1516 <https://github.com/fmtlib/fmt/pull/1516>`_,
|
||||
`#1518 <https://github.com/fmtlib/fmt/pull/1518>`_,
|
||||
`#1519 <https://github.com/fmtlib/fmt/pull/1519>`_,
|
||||
`#1520 <https://github.com/fmtlib/fmt/pull/1520>`_,
|
||||
`#1521 <https://github.com/fmtlib/fmt/pull/1521>`_,
|
||||
`#1522 <https://github.com/fmtlib/fmt/pull/1522>`_,
|
||||
`#1524 <https://github.com/fmtlib/fmt/issues/1524>`_,
|
||||
`#1530 <https://github.com/fmtlib/fmt/pull/1530>`_,
|
||||
`#1531 <https://github.com/fmtlib/fmt/issues/1531>`_,
|
||||
`#1532 <https://github.com/fmtlib/fmt/pull/1532>`_,
|
||||
`#1539 <https://github.com/fmtlib/fmt/issues/1539>`_,
|
||||
`#1547 <https://github.com/fmtlib/fmt/issues/1547>`_,
|
||||
`#1548 <https://github.com/fmtlib/fmt/issues/1548>`_,
|
||||
`#1554 <https://github.com/fmtlib/fmt/pull/1554>`_,
|
||||
`#1567 <https://github.com/fmtlib/fmt/issues/1567>`_,
|
||||
`#1568 <https://github.com/fmtlib/fmt/pull/1568>`_,
|
||||
`#1569 <https://github.com/fmtlib/fmt/pull/1569>`_,
|
||||
`#1571 <https://github.com/fmtlib/fmt/pull/1571>`_,
|
||||
`#1573 <https://github.com/fmtlib/fmt/pull/1573>`_,
|
||||
`#1575 <https://github.com/fmtlib/fmt/pull/1575>`_,
|
||||
`#1581 <https://github.com/fmtlib/fmt/pull/1581>`_,
|
||||
`#1583 <https://github.com/fmtlib/fmt/issues/1583>`_,
|
||||
`#1586 <https://github.com/fmtlib/fmt/issues/1586>`_,
|
||||
`#1587 <https://github.com/fmtlib/fmt/issues/1587>`_,
|
||||
`#1594 <https://github.com/fmtlib/fmt/issues/1594>`_,
|
||||
`#1596 <https://github.com/fmtlib/fmt/pull/1596>`_,
|
||||
`#1604 <https://github.com/fmtlib/fmt/issues/1604>`_,
|
||||
`#1606 <https://github.com/fmtlib/fmt/pull/1606>`_,
|
||||
`#1607 <https://github.com/fmtlib/fmt/issues/1607>`_,
|
||||
`#1609 <https://github.com/fmtlib/fmt/issues/1609>`_).
|
||||
Thanks `@marti4d (Chris Martin) <https://github.com/marti4d>`_,
|
||||
`@iPherian <https://github.com/iPherian>`_,
|
||||
`@parkertomatoes <https://github.com/parkertomatoes>`_,
|
||||
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@DanielaE (Daniela Engert) <https://github.com/DanielaE>`_,
|
||||
`@torsten48 <https://github.com/torsten48>`_,
|
||||
`@tohammer (Tobias Hammer) <https://github.com/tohammer>`_,
|
||||
`@lefticus (Jason Turner) <https://github.com/lefticus>`_,
|
||||
`@ryusakki (Haise) <https://github.com/ryusakki>`_,
|
||||
`@adnsv (Alex Denisov) <https://github.com/adnsv>`_,
|
||||
`@fghzxm <https://github.com/fghzxm>`_,
|
||||
`@refnum <https://github.com/refnum>`_,
|
||||
`@pramodk (Pramod Kumbhar) <https://github.com/pramodk>`_,
|
||||
`@Spirrwell <https://github.com/Spirrwell>`_,
|
||||
`@scramsby (Scott Ramsby) <https://github.com/scramsby>`_.
|
||||
|
||||
6.1.2 - 2019-12-11
|
||||
------------------
|
||||
|
||||
* Fixed ABI compatibility with ``libfmt.so.6.0.0``
|
||||
(`#1471 <https://github.com/fmtlib/fmt/issues/1471>`_).
|
||||
|
||||
* Fixed handling types convertible to ``std::string_view``
|
||||
(`#1451 <https://github.com/fmtlib/fmt/pull/1451>`_).
|
||||
Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_.
|
||||
|
||||
* Made CUDA test an opt-in enabled via the ``FMT_CUDA_TEST`` CMake option.
|
||||
|
||||
* Fixed sign conversion warnings
|
||||
(`#1440 <https://github.com/fmtlib/fmt/pull/1440>`_).
|
||||
Thanks `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_.
|
||||
|
||||
6.1.1 - 2019-12-04
|
||||
------------------
|
||||
|
||||
* Fixed shared library build on Windows
|
||||
(`#1443 <https://github.com/fmtlib/fmt/pull/1443>`_,
|
||||
`#1445 <https://github.com/fmtlib/fmt/issues/1445>`_,
|
||||
`#1446 <https://github.com/fmtlib/fmt/pull/1446>`_,
|
||||
`#1450 <https://github.com/fmtlib/fmt/issues/1450>`_).
|
||||
Thanks `@egorpugin (Egor Pugin) <https://github.com/egorpugin>`_,
|
||||
`@bbolli (Beat Bolli) <https://github.com/bbolli>`_.
|
||||
|
||||
* Added a missing decimal point in exponent notation with trailing zeros.
|
||||
|
||||
* Removed deprecated ``format_arg_store::TYPES``.
|
||||
|
||||
6.1.0 - 2019-12-01
|
||||
------------------
|
||||
|
||||
* {fmt} now formats IEEE 754 ``float`` and ``double`` using the shortest decimal
|
||||
representation with correct rounding by default:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <cmath>
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("{}", M_PI);
|
||||
}
|
||||
|
||||
prints ``3.141592653589793``.
|
||||
|
||||
* Made the fast binary to decimal floating-point formatter the default,
|
||||
simplified it and improved performance. {fmt} is now 15 times faster than
|
||||
libc++'s ``std::ostringstream``, 11 times faster than ``printf`` and 10%
|
||||
faster than double-conversion on `dtoa-benchmark
|
||||
<https://github.com/fmtlib/dtoa-benchmark>`_:
|
||||
|
||||
================== ========= =======
|
||||
Function Time (ns) Speedup
|
||||
================== ========= =======
|
||||
ostringstream 1,346.30 1.00x
|
||||
ostrstream 1,195.74 1.13x
|
||||
sprintf 995.08 1.35x
|
||||
doubleconv 99.10 13.59x
|
||||
fmt 88.34 15.24x
|
||||
================== ========= =======
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/
|
||||
69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
||||
|
||||
* {fmt} no longer converts ``float`` arguments to ``double``. In particular this
|
||||
improves the default (shortest) representation of floats and makes
|
||||
``fmt::format`` consistent with ``std::format`` specs
|
||||
(`#1336 <https://github.com/fmtlib/fmt/issues/1336>`_,
|
||||
`#1353 <https://github.com/fmtlib/fmt/issues/1353>`_,
|
||||
`#1360 <https://github.com/fmtlib/fmt/pull/1360>`_,
|
||||
`#1361 <https://github.com/fmtlib/fmt/pull/1361>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("{}", 0.1f);
|
||||
|
||||
prints ``0.1`` instead of ``0.10000000149011612``.
|
||||
|
||||
Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_.
|
||||
|
||||
* Made floating-point formatting output consistent with ``printf``/iostreams
|
||||
(`#1376 <https://github.com/fmtlib/fmt/issues/1376>`_,
|
||||
`#1417 <https://github.com/fmtlib/fmt/issues/1417>`_).
|
||||
|
||||
* Added support for 128-bit integers
|
||||
(`#1287 <https://github.com/fmtlib/fmt/pull/1287>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("{}", std::numeric_limits<__int128_t>::max());
|
||||
|
||||
prints ``170141183460469231731687303715884105727``.
|
||||
|
||||
Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_.
|
||||
|
||||
* The overload of ``print`` that takes ``text_style`` is now atomic, i.e. the
|
||||
output from different threads doesn't interleave
|
||||
(`#1351 <https://github.com/fmtlib/fmt/pull/1351>`_).
|
||||
Thanks `@tankiJong (Tanki Zhang) <https://github.com/tankiJong>`_.
|
||||
|
||||
* Made compile time in the header-only mode ~20% faster by reducing the number
|
||||
of template instantiations. ``wchar_t`` overload of ``vprint`` was moved from
|
||||
``fmt/core.h`` to ``fmt/format.h``.
|
||||
|
||||
* Added an overload of ``fmt::join`` that works with tuples
|
||||
(`#1322 <https://github.com/fmtlib/fmt/issues/1322>`_,
|
||||
`#1330 <https://github.com/fmtlib/fmt/pull/1330>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <tuple>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
int main() {
|
||||
std::tuple<char, int, float> t{'a', 1, 2.0f};
|
||||
fmt::print("{}", t);
|
||||
}
|
||||
|
||||
prints ``('a', 1, 2.0)``.
|
||||
|
||||
Thanks `@jeremyong (Jeremy Ong) <https://github.com/jeremyong>`_.
|
||||
|
||||
* Changed formatting of octal zero with prefix from "00" to "0":
|
||||
|
||||
.. code:: c++
|
||||
|
||||
fmt::print("{:#o}", 0);
|
||||
|
||||
prints ``0``.
|
||||
|
||||
* The locale is now passed to ostream insertion (``<<``) operators
|
||||
(`#1406 <https://github.com/fmtlib/fmt/pull/1406>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/locale.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
struct S {
|
||||
double value;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, S s) {
|
||||
return os << s.value;
|
||||
}
|
||||
|
||||
int main() {
|
||||
auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42});
|
||||
// s == "0,42"
|
||||
}
|
||||
|
||||
Thanks `@dlaugt (Daniel Laügt) <https://github.com/dlaugt>`_.
|
||||
|
||||
* Locale-specific number formatting now uses grouping
|
||||
(`#1393 <https://github.com/fmtlib/fmt/issues/1393>`_
|
||||
`#1394 <https://github.com/fmtlib/fmt/pull/1394>`_).
|
||||
Thanks `@skrdaniel <https://github.com/skrdaniel>`_.
|
||||
|
||||
* Fixed handling of types with deleted implicit rvalue conversion to
|
||||
``const char**`` (`#1421 <https://github.com/fmtlib/fmt/issues/1421>`_):
|
||||
|
||||
.. code:: c++
|
||||
|
||||
struct mystring {
|
||||
operator const char*() const&;
|
||||
operator const char*() &;
|
||||
operator const char*() const&& = delete;
|
||||
operator const char*() && = delete;
|
||||
};
|
||||
mystring str;
|
||||
fmt::print("{}", str); // now compiles
|
||||
|
||||
* Enums are now mapped to correct underlying types instead of ``int``
|
||||
(`#1286 <https://github.com/fmtlib/fmt/pull/1286>`_).
|
||||
Thanks `@agmt (Egor Seredin) <https://github.com/agmt>`_.
|
||||
|
||||
* Enum classes are no longer implicitly converted to ``int``
|
||||
(`#1424 <https://github.com/fmtlib/fmt/issues/1424>`_).
|
||||
|
||||
* Added ``basic_format_parse_context`` for consistency with C++20
|
||||
``std::format`` and deprecated ``basic_parse_context``.
|
||||
|
||||
* Fixed handling of UTF-8 in precision
|
||||
(`#1389 <https://github.com/fmtlib/fmt/issues/1389>`_,
|
||||
`#1390 <https://github.com/fmtlib/fmt/pull/1390>`_).
|
||||
Thanks `@tajtiattila (Attila Tajti) <https://github.com/tajtiattila>`_.
|
||||
|
||||
* {fmt} can now be installed on Linux, macOS and Windows with
|
||||
`Conda <https://docs.conda.io/en/latest/>`__ using its
|
||||
`conda-forge <https://conda-forge.org>`__
|
||||
`package <https://github.com/conda-forge/fmt-feedstock>`__
|
||||
(`#1410 <https://github.com/fmtlib/fmt/pull/1410>`_)::
|
||||
|
||||
conda install -c conda-forge fmt
|
||||
|
||||
Thanks `@tdegeus (Tom de Geus) <https://github.com/tdegeus>`_.
|
||||
|
||||
* Added a CUDA test (`#1285 <https://github.com/fmtlib/fmt/pull/1285>`_,
|
||||
`#1317 <https://github.com/fmtlib/fmt/pull/1317>`_).
|
||||
Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_ and
|
||||
`@risa2000 <https://github.com/risa2000>`_.
|
||||
|
||||
* Improved documentation (`#1276 <https://github.com/fmtlib/fmt/pull/1276>`_,
|
||||
`#1291 <https://github.com/fmtlib/fmt/issues/1291>`_,
|
||||
`#1296 <https://github.com/fmtlib/fmt/issues/1296>`_,
|
||||
`#1315 <https://github.com/fmtlib/fmt/pull/1315>`_,
|
||||
`#1332 <https://github.com/fmtlib/fmt/pull/1332>`_,
|
||||
`#1337 <https://github.com/fmtlib/fmt/pull/1337>`_,
|
||||
`#1395 <https://github.com/fmtlib/fmt/issues/1395>`_
|
||||
`#1418 <https://github.com/fmtlib/fmt/pull/1418>`_).
|
||||
Thanks
|
||||
`@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_,
|
||||
`@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_,
|
||||
`@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_.
|
||||
|
||||
* Various code improvements
|
||||
(`#1358 <https://github.com/fmtlib/fmt/pull/1358>`_,
|
||||
`#1407 <https://github.com/fmtlib/fmt/pull/1407>`_).
|
||||
Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_,
|
||||
`@dpacbach (David P. Sicilia) <https://github.com/dpacbach>`_,
|
||||
|
||||
* Fixed compile-time format string checks for user-defined types
|
||||
(`#1292 <https://github.com/fmtlib/fmt/issues/1292>`_).
|
||||
|
||||
* Worked around a false positive in ``unsigned-integer-overflow`` sanitizer
|
||||
(`#1377 <https://github.com/fmtlib/fmt/issues/1377>`_).
|
||||
|
||||
* Fixed various warnings and compilation issues
|
||||
(`#1273 <https://github.com/fmtlib/fmt/issues/1273>`_,
|
||||
`#1278 <https://github.com/fmtlib/fmt/pull/1278>`_,
|
||||
`#1280 <https://github.com/fmtlib/fmt/pull/1280>`_,
|
||||
`#1281 <https://github.com/fmtlib/fmt/issues/1281>`_,
|
||||
`#1288 <https://github.com/fmtlib/fmt/issues/1288>`_,
|
||||
`#1290 <https://github.com/fmtlib/fmt/pull/1290>`_,
|
||||
`#1301 <https://github.com/fmtlib/fmt/pull/1301>`_,
|
||||
`#1305 <https://github.com/fmtlib/fmt/issues/1305>`_,
|
||||
`#1306 <https://github.com/fmtlib/fmt/issues/1306>`_,
|
||||
`#1309 <https://github.com/fmtlib/fmt/issues/1309>`_,
|
||||
`#1312 <https://github.com/fmtlib/fmt/pull/1312>`_,
|
||||
`#1313 <https://github.com/fmtlib/fmt/issues/1313>`_,
|
||||
`#1316 <https://github.com/fmtlib/fmt/issues/1316>`_,
|
||||
`#1319 <https://github.com/fmtlib/fmt/issues/1319>`_,
|
||||
`#1320 <https://github.com/fmtlib/fmt/pull/1320>`_,
|
||||
`#1326 <https://github.com/fmtlib/fmt/pull/1326>`_,
|
||||
`#1328 <https://github.com/fmtlib/fmt/pull/1328>`_,
|
||||
`#1344 <https://github.com/fmtlib/fmt/issues/1344>`_,
|
||||
`#1345 <https://github.com/fmtlib/fmt/pull/1345>`_,
|
||||
`#1347 <https://github.com/fmtlib/fmt/pull/1347>`_,
|
||||
`#1349 <https://github.com/fmtlib/fmt/pull/1349>`_,
|
||||
`#1354 <https://github.com/fmtlib/fmt/issues/1354>`_,
|
||||
`#1362 <https://github.com/fmtlib/fmt/issues/1362>`_,
|
||||
`#1366 <https://github.com/fmtlib/fmt/issues/1366>`_,
|
||||
`#1364 <https://github.com/fmtlib/fmt/pull/1364>`_,
|
||||
`#1370 <https://github.com/fmtlib/fmt/pull/1370>`_,
|
||||
`#1371 <https://github.com/fmtlib/fmt/pull/1371>`_,
|
||||
`#1385 <https://github.com/fmtlib/fmt/issues/1385>`_,
|
||||
`#1388 <https://github.com/fmtlib/fmt/issues/1388>`_,
|
||||
`#1397 <https://github.com/fmtlib/fmt/pull/1397>`_,
|
||||
`#1414 <https://github.com/fmtlib/fmt/pull/1414>`_,
|
||||
`#1416 <https://github.com/fmtlib/fmt/pull/1416>`_,
|
||||
`#1422 <https://github.com/fmtlib/fmt/issues/1422>`_
|
||||
`#1427 <https://github.com/fmtlib/fmt/pull/1427>`_,
|
||||
`#1431 <https://github.com/fmtlib/fmt/issues/1431>`_,
|
||||
`#1433 <https://github.com/fmtlib/fmt/pull/1433>`_).
|
||||
Thanks `@hhb <https://github.com/hhb>`_,
|
||||
`@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
|
||||
`@gabime (Gabi Melman) <https://github.com/gabime>`_,
|
||||
`@neheb (Rosen Penev) <https://github.com/neheb>`_,
|
||||
`@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_,
|
||||
`@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_,
|
||||
`@mwinterb <https://github.com/mwinterb>`_,
|
||||
`@orivej (Orivej Desh) <https://github.com/orivej>`_,
|
||||
`@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_
|
||||
`@leonklingele <https://github.com/leonklingele>`_,
|
||||
`@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
|
||||
`@kent-tri <https://github.com/kent-tri>`_,
|
||||
`@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_,
|
||||
`@marti4d (Chris Martin) <https://github.com/marti4d>`_.
|
||||
|
||||
6.0.0 - 2019-08-26
|
||||
------------------
|
||||
|
||||
@ -183,6 +678,7 @@
|
||||
* Modernized the codebase using more C++11 features and removing workarounds.
|
||||
Most importantly, ``buffer_context`` is now an alias template, so
|
||||
use ``buffer_context<T>`` instead of ``buffer_context<T>::type``.
|
||||
These features require GCC 4.8 or later.
|
||||
|
||||
* ``formatter`` specializations now always take precedence over implicit
|
||||
conversions to ``int`` and the undocumented ``convert_to_int`` trait
|
||||
@ -298,7 +794,7 @@
|
||||
`#1222 <https://github.com/fmtlib/fmt/issues/1222>`_).
|
||||
Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
|
||||
|
||||
* Fixed bugs discovered by fuzzing or during fuzzing integation
|
||||
* Fixed bugs discovered by fuzzing or during fuzzing integration
|
||||
(`#1124 <https://github.com/fmtlib/fmt/issues/1124>`_,
|
||||
`#1127 <https://github.com/fmtlib/fmt/issues/1127>`_,
|
||||
`#1132 <https://github.com/fmtlib/fmt/issues/1132>`_,
|
||||
|
97
README.rst
97
README.rst
@ -7,16 +7,20 @@
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
|
||||
:alt: fmt is continuously fuzzed att oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dlibfmt&can=1
|
||||
|
||||
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: http://stackoverflow.com/questions/tagged/fmt
|
||||
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
**{fmt}** is an open-source formatting library for C++.
|
||||
It can be used as a safe and fast alternative to (s)printf and iostreams.
|
||||
|
||||
`Documentation <https://fmt.dev/latest/>`__
|
||||
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt <http://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt <https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
|
||||
Features
|
||||
--------
|
||||
@ -24,27 +28,27 @@ Features
|
||||
* Replacement-based `format API <https://fmt.dev/dev/api.html>`_ with
|
||||
positional arguments for localization.
|
||||
* `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
|
||||
of `str.format <https://docs.python.org/2/library/stdtypes.html#str.format>`_
|
||||
of `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
in Python.
|
||||
* Safe `printf implementation
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including
|
||||
the POSIX extension for positional arguments.
|
||||
* Implementation of `C++20 std::format <https://fmt.dev/Text%20Formatting.html>`__.
|
||||
* Implementation of `C++20 std::format <https://en.cppreference.com/w/cpp/utility/format>`__.
|
||||
* Support for user-defined types.
|
||||
* High performance: faster than common standard library implementations of
|
||||
`printf <http://en.cppreference.com/w/cpp/io/c/fprintf>`_ and
|
||||
`printf <https://en.cppreference.com/w/cpp/io/c/fprintf>`_ and
|
||||
iostreams. See `Speed tests`_ and `Fast integer to string conversion in C++
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
* Small code size both in terms of source code (the minimum configuration
|
||||
consists of just three header files, ``core.h``, ``format.h`` and
|
||||
``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
|
||||
* Reliability: the library has an extensive set of `unit tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_.
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors.
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
permissive BSD `license
|
||||
permissive MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||
consistent output across platforms and support for older compilers.
|
||||
@ -77,16 +81,15 @@ Check a format string at compile time:
|
||||
.. code:: c++
|
||||
|
||||
// test.cc
|
||||
#define FMT_STRING_ALIAS 1
|
||||
#include <fmt/format.h>
|
||||
std::string s = format(fmt("{2}"), 42);
|
||||
std::string s = format(FMT_STRING("{2}"), 42);
|
||||
|
||||
.. code::
|
||||
|
||||
$ c++ -Iinclude -std=c++14 test.cc
|
||||
...
|
||||
test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
|
||||
std::string s = format(fmt("{2}"), 42);
|
||||
std::string s = format(FMT_STRING("{2}"), 42);
|
||||
^
|
||||
include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
|
||||
ErrorHandler::on_error(message);
|
||||
@ -118,11 +121,10 @@ Format objects of user-defined types via a simple `extension API
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<date> {
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
|
||||
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const date &d, FormatContext &ctx) {
|
||||
auto format(const date& d, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
|
||||
}
|
||||
};
|
||||
@ -138,12 +140,12 @@ which take arbitrary arguments (`godbolt <https://godbolt.org/g/MHjHVf>`_):
|
||||
.. code:: c++
|
||||
|
||||
// Prints formatted error message.
|
||||
void vreport_error(const char *format, fmt::format_args args) {
|
||||
void vreport_error(const char* format, fmt::format_args args) {
|
||||
fmt::print("Error: ");
|
||||
fmt::vprint(format, args);
|
||||
}
|
||||
template <typename... Args>
|
||||
void report_error(const char *format, const Args & ... args) {
|
||||
void report_error(const char* format, const Args & ... args) {
|
||||
vreport_error(format, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
@ -162,18 +164,17 @@ Speed tests
|
||||
================= ============= ===========
|
||||
Library Method Run Time, s
|
||||
================= ============= ===========
|
||||
libc printf 1.01
|
||||
libc++ std::ostream 3.04
|
||||
{fmt} 1632f72 fmt::print 0.86
|
||||
tinyformat 2.0.1 tfm::printf 3.23
|
||||
Boost Format 1.67 boost::format 7.98
|
||||
libc printf 1.04
|
||||
libc++ std::ostream 3.05
|
||||
{fmt} 6.1.1 fmt::print 0.75
|
||||
Boost Format 1.67 boost::format 7.24
|
||||
Folly Format folly::format 2.23
|
||||
================= ============= ===========
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, ~17% faster than ``printf``.
|
||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||
|
||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
10.14.3 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
|
||||
10.14.6 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
|
||||
three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||
further details refer to the `source
|
||||
@ -183,7 +184,7 @@ further details refer to the `source
|
||||
formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
and as fast as `double-conversion <https://github.com/google/double-conversion>`_:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg
|
||||
.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang10.0.html
|
||||
|
||||
Compile time and code bloat
|
||||
@ -207,7 +208,6 @@ printf 2.6 29 26
|
||||
printf+string 16.4 29 26
|
||||
iostreams 31.1 59 55
|
||||
{fmt} 19.0 37 34
|
||||
tinyformat 44.0 103 97
|
||||
Boost Format 91.9 226 203
|
||||
Folly Format 115.7 101 88
|
||||
============= =============== ==================== ==================
|
||||
@ -228,14 +228,13 @@ printf 2.2 33 30
|
||||
printf+string 16.0 33 30
|
||||
iostreams 28.3 56 52
|
||||
{fmt} 18.2 59 50
|
||||
tinyformat 32.6 88 82
|
||||
Boost Format 54.1 365 303
|
||||
Folly Format 79.9 445 430
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||
compare formatting function overhead only. Boost Format and tinyformat are
|
||||
header-only libraries so they don't provide any linkage options.
|
||||
compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn't provide any linkage options.
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
@ -265,7 +264,7 @@ or the bloat test::
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <http://play0ad.com/>`_: A free, open-source, cross-platform real-time
|
||||
* `0 A.D. <https://play0ad.com/>`_: A free, open-source, cross-platform real-time
|
||||
strategy game
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
@ -278,21 +277,23 @@ Projects using this library
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: A scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: A compiler cache
|
||||
|
||||
* `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <http://kbengine.org/>`_: An open-source MMOG server engine
|
||||
* `KBEngine <https://kbengine.org/>`_: An open-source MMOG server engine
|
||||
|
||||
* `Keypirinha <http://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
|
||||
|
||||
* `Lifeline <https://github.com/peter-clark/lifeline>`_: A 2D game
|
||||
|
||||
* `Drake <http://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)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
@ -305,10 +306,10 @@ Projects using this library
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: A small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <http://openspaceproject.com/>`_: An open-source astrovisualization
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: An open-source astrovisualization
|
||||
framework
|
||||
|
||||
* `PenUltima Online (POL) <http://www.polserver.com/>`_:
|
||||
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||
An MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: A distributed, high-performance,
|
||||
@ -325,10 +326,10 @@ Projects using this library
|
||||
* `Saddy <https://github.com/mamontov-cpp/saddy-graphics-engine-2d>`_:
|
||||
Small crossplatform 2D graphic engine
|
||||
|
||||
* `Salesforce Analytics Cloud <http://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
* `Salesforce Analytics Cloud <https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
Business intelligence software
|
||||
|
||||
* `Scylla <http://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
|
||||
|
||||
* `Seastar <http://www.seastar-project.org/>`_: An advanced, open-source C++
|
||||
@ -343,7 +344,7 @@ Projects using this library
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
|
||||
MMORPG framework
|
||||
|
||||
`More... <https://github.com/search?q=cppformat&type=Code>`_
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
@ -367,7 +368,7 @@ The good thing about ``printf`` is that it is pretty fast and readily available
|
||||
being a part of the C standard library. The main drawback is that it
|
||||
doesn't support user-defined types. ``printf`` also has safety issues although
|
||||
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
|
||||
<http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
There is a POSIX extension that adds positional arguments required for
|
||||
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||
to ``printf`` but it is not a part of C99 and may not be available on some
|
||||
@ -419,20 +420,6 @@ arguments. However it has significant limitations, citing its author:
|
||||
It is also quite big and has a heavy dependency, STLSoft, which might be
|
||||
too restrictive for using it in some projects.
|
||||
|
||||
Loki SafeFormat
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
SafeFormat is a formatting library which uses ``printf``-like format strings and
|
||||
is type safe. It doesn't support user-defined types or positional arguments and
|
||||
makes unconventional use of ``operator()`` for passing format arguments.
|
||||
|
||||
Tinyformat
|
||||
~~~~~~~~~~
|
||||
|
||||
This library supports ``printf``-like format strings and is very small .
|
||||
It doesn't support positional arguments and wrapping it in C++98 is somewhat
|
||||
difficult. Tinyformat relies on iostreams which limits its performance.
|
||||
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -469,7 +456,7 @@ A: use ``std::tuple``:
|
||||
License
|
||||
-------
|
||||
|
||||
{fmt} is distributed under the BSD `license
|
||||
{fmt} is distributed under the MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
The `Format String Syntax
|
||||
@ -499,10 +486,10 @@ written by Chris Foster. Boost Format library is acknowledged transitively
|
||||
since it had some influence on tinyformat.
|
||||
Some ideas used in the implementation are borrowed from `Loki
|
||||
<http://loki-lib.sourceforge.net/>`_ SafeFormat and `Diagnostic API
|
||||
<http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
|
||||
`Clang <http://clang.llvm.org/>`_.
|
||||
<https://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
|
||||
`Clang <https://clang.llvm.org/>`_.
|
||||
Format string syntax and the documentation are based on Python's `str.format
|
||||
<http://docs.python.org/2/library/stdtypes.html#str.format>`_.
|
||||
<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
|
||||
Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
|
||||
comments and contribution to the design of the type-safe API and
|
||||
`Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
|
||||
|
2
doc/_templates/layout.html
vendored
2
doc/_templates/layout.html
vendored
@ -84,7 +84,7 @@
|
||||
<div class="jumbotron">
|
||||
<div class="tb-container">
|
||||
<h1>{fmt}</h1>
|
||||
<p class="lead">Small, safe and fast formatting library</p>
|
||||
<p class="lead">A modern formatting library</p>
|
||||
<div class="btn-group" role="group">
|
||||
{% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
|
||||
<a class="btn btn-success"
|
||||
|
128
doc/api.rst
128
doc/api.rst
@ -10,12 +10,14 @@ The {fmt} library API consists of the following parts:
|
||||
facilities and a lightweight subset of formatting functions
|
||||
* :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
|
||||
format string checks, output iterator and user-defined type support
|
||||
* :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/ostream.h <ostream-api>`: ``std::ostream`` support
|
||||
* :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
|
||||
|
||||
All functions and types provided by the library reside in namespace ``fmt`` and
|
||||
macros have prefix ``FMT_`` or ``fmt``.
|
||||
macros have prefix ``FMT_``.
|
||||
|
||||
.. _core-api:
|
||||
|
||||
@ -23,7 +25,8 @@ Core API
|
||||
========
|
||||
|
||||
``fmt/core.h`` defines the core API which provides argument handling facilities
|
||||
and a lightweight subset of formatting functions.
|
||||
and a lightweight subset of formatting functions. In the header-only mode
|
||||
include ``fmt/format.h`` instead of ``fmt/core.h``.
|
||||
|
||||
The following functions use :ref:`format string syntax <syntax>`
|
||||
similar to that of Python's `str.format
|
||||
@ -49,7 +52,6 @@ participate in an overload resolution if the latter is not a string.
|
||||
|
||||
.. doxygenfunction:: print(std::FILE *, const S&, Args&&...)
|
||||
.. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
|
||||
.. doxygenfunction:: vprint(std::FILE *, wstring_view, wformat_args)
|
||||
|
||||
Named Arguments
|
||||
---------------
|
||||
@ -83,6 +85,19 @@ Compatibility
|
||||
.. doxygentypedef:: fmt::string_view
|
||||
.. doxygentypedef:: fmt::wstring_view
|
||||
|
||||
Locale
|
||||
------
|
||||
|
||||
All formatting is locale-independent by default. Use the ``'n'`` format
|
||||
specifier to insert the appropriate number separator characters from the
|
||||
locale::
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <locale>
|
||||
|
||||
std::locale::global(std::locale("en_US.UTF-8"));
|
||||
auto s = fmt::format("{:n}", 1000000); // s == "1,000,000"
|
||||
|
||||
.. _format-api:
|
||||
|
||||
Format API
|
||||
@ -94,8 +109,11 @@ string checks, output iterator and user-defined type support.
|
||||
Compile-time Format String Checks
|
||||
---------------------------------
|
||||
|
||||
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
|
||||
.. doxygendefine:: fmt
|
||||
|
||||
Formatting User-defined Types
|
||||
-----------------------------
|
||||
@ -107,32 +125,56 @@ template and implement ``parse`` and ``format`` methods::
|
||||
|
||||
struct point { double x, y; };
|
||||
|
||||
namespace fmt {
|
||||
template <>
|
||||
struct formatter<point> {
|
||||
template <typename ParseContext>
|
||||
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
|
||||
struct fmt::formatter<point> {
|
||||
// Presentation format: 'f' - fixed, 'e' - exponential.
|
||||
char presentation = 'f';
|
||||
|
||||
// Parses format specifications of the form ['f' | 'e'].
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
// [ctx.begin(), ctx.end()) is a character range that contains a part of
|
||||
// the format string starting from the format specifications to be parsed,
|
||||
// e.g. in
|
||||
//
|
||||
// fmt::format("{:f} - point of interest", point{1, 2});
|
||||
//
|
||||
// the range will contain "f} - point of interest". The formatter should
|
||||
// parse specifiers until '}' or the end of the range. In this example
|
||||
// the formatter should parse the 'f' specifier and return an iterator
|
||||
// pointing to '}'.
|
||||
|
||||
// Parse the presentation format and store it in the formatter:
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
|
||||
|
||||
// Check if reached the end of the range:
|
||||
if (it != end && *it != '}')
|
||||
throw format_error("invalid format");
|
||||
|
||||
// Return an iterator past the end of the parsed range:
|
||||
return it;
|
||||
}
|
||||
|
||||
// Formats the point p using the parsed format specification (presentation)
|
||||
// stored in this formatter.
|
||||
template <typename FormatContext>
|
||||
auto format(const point &p, FormatContext &ctx) {
|
||||
return format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y);
|
||||
auto format(const point& p, FormatContext& ctx) {
|
||||
// ctx.out() is an output iterator to write to.
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
|
||||
p.x, p.y);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Then you can pass objects of type ``point`` to any formatting function::
|
||||
|
||||
point p = {1, 2};
|
||||
std::string s = fmt::format("{}", p);
|
||||
std::string s = fmt::format("{:f}", p);
|
||||
// s == "(1.0, 2.0)"
|
||||
|
||||
In the example above the ``formatter<point>::parse`` function ignores the
|
||||
contents of the format string referred to by ``ctx.begin()`` so the object will
|
||||
always be formatted in the same way. See ``formatter<tm>::parse`` in
|
||||
:file:`fmt/chrono.h` for an advanced example of how to parse the format string and
|
||||
customize the formatted output.
|
||||
|
||||
You can also reuse existing formatters, for example::
|
||||
You can also reuse existing formatters via inheritance or composition, for
|
||||
example::
|
||||
|
||||
enum class color {red, green, blue};
|
||||
|
||||
@ -140,7 +182,7 @@ You can also reuse existing formatters, for example::
|
||||
struct fmt::formatter<color>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(color c, FormatContext &ctx) {
|
||||
auto format(color c, FormatContext& ctx) {
|
||||
string_view name = "unknown";
|
||||
switch (c) {
|
||||
case color::red: name = "red"; break;
|
||||
@ -180,6 +222,9 @@ You can also write a formatter for a hierarchy of classes::
|
||||
fmt::print("{}", a); // prints "B"
|
||||
}
|
||||
|
||||
.. doxygenclass:: fmt::basic_format_parse_context
|
||||
:members:
|
||||
|
||||
Output Iterator Support
|
||||
-----------------------
|
||||
|
||||
@ -263,7 +308,7 @@ allocator::
|
||||
template <typename ...Args>
|
||||
inline custom_string format(custom_allocator alloc,
|
||||
fmt::string_view format_str,
|
||||
const Args & ... args) {
|
||||
const Args& ... args) {
|
||||
return vformat(alloc, format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
@ -278,21 +323,21 @@ Custom Formatting of Built-in Types
|
||||
It is possible to change the way arguments are formatted by providing a
|
||||
custom argument formatter class::
|
||||
|
||||
using arg_formatter =
|
||||
fmt::arg_formatter<fmt::back_insert_range<fmt::internal::buffer>>;
|
||||
using arg_formatter = fmt::arg_formatter<fmt::buffer_range<char>>;
|
||||
|
||||
// A custom argument formatter that formats negative integers as unsigned
|
||||
// with the ``x`` format specifier.
|
||||
class custom_arg_formatter : public arg_formatter {
|
||||
public:
|
||||
custom_arg_formatter(fmt::format_context &ctx,
|
||||
fmt::format_specs *spec = nullptr)
|
||||
: arg_formatter(ctx, spec) {}
|
||||
custom_arg_formatter(fmt::format_context& ctx,
|
||||
fmt::format_parse_context* parse_ctx = nullptr,
|
||||
fmt::format_specs* spec = nullptr)
|
||||
: arg_formatter(ctx, parse_ctx, spec) {}
|
||||
|
||||
using arg_formatter::operator();
|
||||
|
||||
auto operator()(int value) {
|
||||
if (spec().type() == 'x')
|
||||
if (specs() && specs()->type == 'x')
|
||||
return (*this)(static_cast<unsigned>(value)); // convert to unsigned and format
|
||||
return arg_formatter::operator()(value);
|
||||
}
|
||||
@ -307,7 +352,7 @@ custom argument formatter class::
|
||||
|
||||
template <typename ...Args>
|
||||
inline std::string custom_format(
|
||||
fmt::string_view format_str, const Args &... args) {
|
||||
fmt::string_view format_str, const Args&... args) {
|
||||
return custom_vformat(format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
@ -316,6 +361,31 @@ custom argument formatter class::
|
||||
.. doxygenclass:: fmt::arg_formatter
|
||||
:members:
|
||||
|
||||
.. _ranges-api:
|
||||
|
||||
Ranges and Tuple Formatting
|
||||
===========================
|
||||
|
||||
The library also supports convenient formatting of ranges and tuples::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<char, int, float> t{'a', 1, 2.0f};
|
||||
// Prints "('a', 1, 2.0)"
|
||||
fmt::print("{}", t);
|
||||
|
||||
|
||||
NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
|
||||
``format.h`` header, but expect this to change in the future.
|
||||
|
||||
Using ``fmt::join``, you can separate tuple elements with a custom separator::
|
||||
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
// Prints "1, a"
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
|
||||
.. _chrono-api:
|
||||
|
||||
Date and Time Formatting
|
||||
@ -349,7 +419,7 @@ user-defined types that have overloaded ``operator<<``::
|
||||
public:
|
||||
date(int year, int month, int day): year_(year), month_(month), day_(day) {}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const date &d) {
|
||||
friend std::ostream& operator<<(std::ostream& os, const date& d) {
|
||||
return os << d.year_ << '-' << d.month_ << '-' << d.day_;
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import errno, os, shutil, sys, tempfile
|
||||
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']
|
||||
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']
|
||||
|
||||
def pip_install(package, commit=None, **kwargs):
|
||||
"Install package using pip."
|
||||
@ -74,7 +74,7 @@ def build_docs(version='dev', **kwargs):
|
||||
GENERATE_MAN = NO
|
||||
GENERATE_RTF = NO
|
||||
CASE_SENSE_NAMES = NO
|
||||
INPUT = {0}/core.h {0}/format.h {0}/ostream.h \
|
||||
INPUT = {0}/core.h {0}/format.h {0}/os.h {0}/ostream.h \
|
||||
{0}/printf.h {0}/time.h
|
||||
QUIET = YES
|
||||
JAVADOC_AUTOBRIEF = YES
|
||||
|
@ -1,17 +1,18 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
**fmt** (formerly cppformat) is an open-source formatting library.
|
||||
It can be used as a fast and safe alternative to printf and IOStreams.
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">What users say:</div>
|
||||
<div class="panel-body">
|
||||
Thanks for creating this library. It’s been a hole in C++ for a long
|
||||
time. I’ve used both boost::format and loki::SPrintf, and neither felt
|
||||
like the right answer. This does.
|
||||
Thanks for creating this library. It’s been a hole in C++ for
|
||||
a long time. I’ve used both <code>boost::format</code> and
|
||||
<code>loki::SPrintf</code>, and neither felt like the right answer.
|
||||
This does.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -20,12 +21,13 @@ It can be used as a fast and safe alternative to printf and IOStreams.
|
||||
Format API
|
||||
----------
|
||||
|
||||
The replacement-based Format API provides a safe alternative to ``printf``,
|
||||
``sprintf`` and friends with comparable or `better performance
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
|
||||
The format API is similar in spirit to the C ``printf`` family of function but
|
||||
is safer, simpler and serveral times `faster
|
||||
<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
|
||||
than common standard library implementations.
|
||||
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 Python:
|
||||
`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in
|
||||
Python:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -60,7 +62,7 @@ The Format API also supports positional arguments useful for localization:
|
||||
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
|
||||
what goes where when multiple values are being inserted:
|
||||
what goes where when multiple arguments are being formatted:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
@ -72,21 +74,10 @@ an alternative, slightly terser syntax for named arguments:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
using namespace fmt::literals;
|
||||
fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
|
||||
"name"_a="World", "number"_a=42);
|
||||
|
||||
The ``_format`` suffix may be used to format string literals similar to Python:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string message = "{0}{1}{0}"_format("abra", "cad");
|
||||
|
||||
Other than the placement of the format string on the left of the operator,
|
||||
``_format`` is functionally identical to ``fmt::format``. In order to use the
|
||||
literal operators, they must be made visible with the directive
|
||||
``using namespace fmt::literals;``. Note that this brings in only ``_a`` and
|
||||
``_format`` but nothing else from the ``fmt`` namespace.
|
||||
|
||||
.. _safety:
|
||||
|
||||
Safety
|
||||
@ -106,11 +97,10 @@ string", because the argument ``"forty-two"`` is a string while the format code
|
||||
|
||||
.. code:: c++
|
||||
|
||||
format(fmt("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
|
||||
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for how to enable
|
||||
compile-time checks.
|
||||
relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details.
|
||||
|
||||
The following code
|
||||
|
||||
@ -178,13 +168,13 @@ The library is highly portable and relies only on a small set of C++11 features:
|
||||
* alias templates
|
||||
|
||||
These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older
|
||||
compilers use fmt `version 4.x
|
||||
compilers use {fmt} `version 4.x
|
||||
<https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
|
||||
maintained and only requires C++98.
|
||||
|
||||
The output of all formatting functions is consistent across platforms. In
|
||||
particular, formatting a floating-point infinity always gives ``inf`` while the
|
||||
output of ``printf`` is platform-dependent in this case. For example,
|
||||
output of ``printf`` is platform-dependent. For example,
|
||||
|
||||
.. code::
|
||||
|
||||
@ -197,9 +187,9 @@ always prints ``inf``.
|
||||
Ease of Use
|
||||
-----------
|
||||
|
||||
fmt has a small self-contained code base with the core library consisting of
|
||||
{fmt} has a small self-contained code base with the core library consisting of
|
||||
just three header files and no external dependencies.
|
||||
A permissive BSD `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.
|
||||
|
||||
.. raw:: html
|
||||
|
130
doc/syntax.rst
130
doc/syntax.rst
@ -76,19 +76,19 @@ The general form of a *standard format specifier* is:
|
||||
|
||||
.. productionlist:: sf
|
||||
format_spec: [[`fill`]`align`][`sign`]["#"]["0"][`width`]["." `precision`][`type`]
|
||||
fill: <a character other than '{', '}' or '\0'>
|
||||
align: "<" | ">" | "=" | "^"
|
||||
fill: <a character other than '{' or '}'>
|
||||
align: "<" | ">" | "^"
|
||||
sign: "+" | "-" | " "
|
||||
width: `integer` | "{" `arg_id` "}"
|
||||
precision: `integer` | "{" `arg_id` "}"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X"
|
||||
type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s"
|
||||
int_type: "b" | "B" | "d" | "o" | "x" | "X"
|
||||
|
||||
The *fill* character can be any character other than '{', '}' or '\\0'. The
|
||||
presence of a fill character is signaled by the character following it, which
|
||||
must be one of the alignment options. If the second character of *format_spec*
|
||||
is not a valid alignment option, then it is assumed that both the fill character
|
||||
and the alignment option are absent.
|
||||
The *fill* character can be any Unicode code point other than ``'{'`` or
|
||||
``'}'``. The presence of a fill character is signaled by the character following
|
||||
it, which must be one of the alignment options. If the second character of
|
||||
*format_spec* is not a valid alignment option, then it is assumed that both the
|
||||
fill character and the alignment option are absent.
|
||||
|
||||
The meaning of the various alignment options is as follows:
|
||||
|
||||
@ -101,11 +101,6 @@ The meaning of the various alignment options is as follows:
|
||||
| ``'>'`` | Forces the field to be right-aligned within the |
|
||||
| | available space (this is the default for numbers). |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'='`` | Forces the padding to be placed after the sign (if any) |
|
||||
| | but before the digits. This is used for printing fields |
|
||||
| | in the form '+000000120'. This alignment option is only |
|
||||
| | valid for numeric types. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'^'`` | Forces the field to be centered within the available |
|
||||
| | space. |
|
||||
+---------+----------------------------------------------------------+
|
||||
@ -148,15 +143,17 @@ conversions, trailing zeros are not removed from the result.
|
||||
.. ifconfig:: False
|
||||
|
||||
The ``','`` option signals the use of a comma for a thousands separator.
|
||||
For a locale aware separator, use the ``'n'`` integer presentation type
|
||||
For a locale aware separator, use the ``'L'`` integer presentation type
|
||||
instead.
|
||||
|
||||
*width* is a decimal integer defining the minimum field width. If not
|
||||
specified, then the field width will be determined by the content.
|
||||
|
||||
Preceding the *width* field by a zero (``'0'``) character enables
|
||||
sign-aware zero-padding for numeric types. This is equivalent to a *fill*
|
||||
character of ``'0'`` with an *alignment* type of ``'='``.
|
||||
Preceding the *width* field by a zero (``'0'``) character enables sign-aware
|
||||
zero-padding for numeric types. It forces the padding to be placed after the
|
||||
sign or base (if any) but before the digits. This is used for printing fields in
|
||||
the form '+000000120'. This option is only valid for numeric types and it has no
|
||||
effect on formatting of infinity and NaN.
|
||||
|
||||
The *precision* is a decimal number indicating how many digits should be
|
||||
displayed after the decimal point for a floating-point value formatted with
|
||||
@ -217,9 +214,9 @@ The available integer presentation types are:
|
||||
| | ``'#'`` option with this type adds the prefix ``"0X"`` |
|
||||
| | to the output value. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'n'`` | Number. This is the same as ``'d'``, except that it uses |
|
||||
| | the current locale setting to insert the appropriate |
|
||||
| | number separator characters. |
|
||||
| ``'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'``. |
|
||||
+---------+----------------------------------------------------------+
|
||||
@ -264,9 +261,9 @@ The available presentation types for floating-point values are:
|
||||
| | ``'E'`` if the number gets too large. The |
|
||||
| | representations of infinity and NaN are uppercased, too. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| ``'%'`` | Fixed point as a percentage. This is similar to ``'f'``, |
|
||||
| | but the argument is multiplied by 100 and a percent sign |
|
||||
| | ``%`` is appended. |
|
||||
| ``'L'`` | Locale-specific format. This is the same as ``'g'``, |
|
||||
| | except that it uses the current locale setting to insert |
|
||||
| | the appropriate number separator characters. |
|
||||
+---------+----------------------------------------------------------+
|
||||
| none | Similar to ``'g'``, except that fixed-point notation, |
|
||||
| | when used, has at least one digit past the decimal |
|
||||
@ -323,79 +320,94 @@ following examples.
|
||||
|
||||
Accessing arguments by position::
|
||||
|
||||
format("{0}, {1}, {2}", 'a', 'b', 'c');
|
||||
fmt::format("{0}, {1}, {2}", 'a', 'b', 'c');
|
||||
// Result: "a, b, c"
|
||||
format("{}, {}, {}", 'a', 'b', 'c');
|
||||
fmt::format("{}, {}, {}", 'a', 'b', 'c');
|
||||
// Result: "a, b, c"
|
||||
format("{2}, {1}, {0}", 'a', 'b', 'c');
|
||||
fmt::format("{2}, {1}, {0}", 'a', 'b', 'c');
|
||||
// Result: "c, b, a"
|
||||
format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated
|
||||
fmt::format("{0}{1}{0}", "abra", "cad"); // arguments' indices can be repeated
|
||||
// Result: "abracadabra"
|
||||
|
||||
Aligning the text and specifying a width::
|
||||
|
||||
format("{:<30}", "left aligned");
|
||||
fmt::format("{:<30}", "left aligned");
|
||||
// Result: "left aligned "
|
||||
format("{:>30}", "right aligned");
|
||||
fmt::format("{:>30}", "right aligned");
|
||||
// Result: " right aligned"
|
||||
format("{:^30}", "centered");
|
||||
fmt::format("{:^30}", "centered");
|
||||
// Result: " centered "
|
||||
format("{:*^30}", "centered"); // use '*' as a fill char
|
||||
fmt::format("{:*^30}", "centered"); // use '*' as a fill char
|
||||
// Result: "***********centered***********"
|
||||
|
||||
Dynamic width::
|
||||
|
||||
format("{:<{}}", "left aligned", 30);
|
||||
fmt::format("{:<{}}", "left aligned", 30);
|
||||
// Result: "left aligned "
|
||||
|
||||
Dynamic precision::
|
||||
|
||||
format("{:.{}f}", 3.14, 1);
|
||||
fmt::format("{:.{}f}", 3.14, 1);
|
||||
// Result: "3.1"
|
||||
|
||||
Replacing ``%+f``, ``%-f``, and ``% f`` and specifying a sign::
|
||||
|
||||
format("{:+f}; {:+f}", 3.14, -3.14); // show it always
|
||||
fmt::format("{:+f}; {:+f}", 3.14, -3.14); // show it always
|
||||
// Result: "+3.140000; -3.140000"
|
||||
format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers
|
||||
fmt::format("{: f}; {: f}", 3.14, -3.14); // show a space for positive numbers
|
||||
// Result: " 3.140000; -3.140000"
|
||||
format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}'
|
||||
fmt::format("{:-f}; {:-f}", 3.14, -3.14); // show only the minus -- same as '{:f}; {:f}'
|
||||
// Result: "3.140000; -3.140000"
|
||||
|
||||
As a percentage::
|
||||
|
||||
format("{0:f} or {0:%}", .635);
|
||||
// Result: "0.635000 or 63.500000%"
|
||||
format("{:*^{}.{}%}", 1., 15, 2); // With fill, dynamic width and dynamic precision.
|
||||
// Result: "****100.00%****"
|
||||
|
||||
Replacing ``%x`` and ``%o`` and converting the value to different bases::
|
||||
|
||||
format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
fmt::format("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
|
||||
// Result: "int: 42; hex: 2a; oct: 52; bin: 101010"
|
||||
// with 0x or 0 or 0b as prefix:
|
||||
format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
|
||||
fmt::format("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}", 42);
|
||||
// Result: "int: 42; hex: 0x2a; oct: 052; bin: 0b101010"
|
||||
|
||||
Padded hex byte with prefix and always prints both hex characters::
|
||||
|
||||
format("{:#04x}", 0);
|
||||
fmt::format("{:#04x}", 0);
|
||||
// Result: "0x00"
|
||||
|
||||
Box drawing using Unicode fill::
|
||||
|
||||
fmt::print(
|
||||
"┌{0:─^{2}}┐\n"
|
||||
"│{1: ^{2}}│\n"
|
||||
"└{0:─^{2}}┘\n", "", "Hello, world!", 20);
|
||||
|
||||
prints::
|
||||
|
||||
┌────────────────────┐
|
||||
│ Hello, world! │
|
||||
└────────────────────┘
|
||||
|
||||
Using type-specific formatting::
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
auto t = tm();
|
||||
t.tm_year = 2010 - 1900;
|
||||
t.tm_mon = 6;
|
||||
t.tm_mday = 4;
|
||||
t.tm_hour = 12;
|
||||
t.tm_min = 15;
|
||||
t.tm_sec = 58;
|
||||
fmt::print("{:%Y-%m-%d %H:%M:%S}", t);
|
||||
// Prints: 2010-08-04 12:15:58
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
|
||||
#include <fmt/locale.h>
|
||||
|
||||
auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890);
|
||||
// s == "1,234,567,890"
|
||||
|
||||
.. ifconfig:: False
|
||||
|
||||
Using the comma as a thousands separator::
|
||||
|
||||
format("{:,}", 1234567890);
|
||||
'1,234,567,890'
|
||||
|
||||
Using type-specific formatting::
|
||||
|
||||
>>> import datetime
|
||||
>>> d = datetime.datetime(2010, 7, 4, 12, 15, 58)
|
||||
Format("{:%Y-%m-%d %H:%M:%S}") << d)
|
||||
'2010-07-04 12:15:58'
|
||||
|
||||
Nesting arguments and more complex examples::
|
||||
|
||||
>>> for align, text in zip('<^>', ['left', 'center', 'right']):
|
||||
|
@ -103,7 +103,33 @@ the previous section. Then compile the ``doc`` target/project, for example::
|
||||
make doc
|
||||
|
||||
This will generate the HTML documentation in ``doc/html``.
|
||||
|
||||
|
||||
Conda
|
||||
=====
|
||||
|
||||
fmt can be installed on Linux, macOS and Windows with
|
||||
`Conda <https://docs.conda.io/en/latest/>`__, using its
|
||||
`conda-forge <https://conda-forge.org>`__
|
||||
`package <https://github.com/conda-forge/fmt-feedstock>`__, as follows::
|
||||
|
||||
conda install -c conda-forge fmt
|
||||
|
||||
Vcpkg
|
||||
=====
|
||||
|
||||
You can download and install fmt using the `vcpkg
|
||||
<https://github.com/Microsoft/vcpkg>`__ dependency manager::
|
||||
|
||||
git clone https://github.com/Microsoft/vcpkg.git
|
||||
cd vcpkg
|
||||
./bootstrap-vcpkg.sh
|
||||
./vcpkg integrate install
|
||||
./vcpkg install fmt
|
||||
|
||||
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
|
||||
request <https://github.com/Microsoft/vcpkg>`__ on the vcpkg repository.
|
||||
|
||||
Android NDK
|
||||
===========
|
||||
|
||||
|
@ -8,24 +8,299 @@
|
||||
#ifndef FMT_CHRONO_H_
|
||||
#define FMT_CHRONO_H_
|
||||
|
||||
#include "format.h"
|
||||
#include "locale.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
// enable safe chrono durations, unless explicitly disabled
|
||||
#include "format.h"
|
||||
#include "locale.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
// Enable safe chrono durations, unless explicitly disabled.
|
||||
#ifndef FMT_SAFE_DURATION_CAST
|
||||
# define FMT_SAFE_DURATION_CAST 1
|
||||
#endif
|
||||
|
||||
#if FMT_SAFE_DURATION_CAST
|
||||
# include "safe-duration-cast.h"
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
// For conversion between std::chrono::durations without undefined
|
||||
// behaviour or erroneous results.
|
||||
// This is a stripped down version of duration_cast, for inclusion in fmt.
|
||||
// See https://github.com/pauldreik/safe_duration_cast
|
||||
//
|
||||
// Copyright Paul Dreik 2019
|
||||
namespace safe_duration_cast {
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed ==
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
// A and B are both signed, or both unsigned.
|
||||
if (F::digits <= T::digits) {
|
||||
// From fits in To without any problem.
|
||||
} else {
|
||||
// From does not always fit in To, resort to a dynamic check.
|
||||
if (from < T::min() || from > T::max()) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts From to To, without loss. If the dynamic value of from
|
||||
* can't be converted to To without loss, ec is set.
|
||||
*/
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed !=
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (fmt::internal::is_negative(from)) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// From is positive. Can it always fit in 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())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!F::is_signed && T::is_signed) {
|
||||
// can from be held in 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;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reaching here means all is ok for lossless conversion.
|
||||
return static_cast<To>(from);
|
||||
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
return from;
|
||||
} // function
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* converts From to To if possible, otherwise ec is set.
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------|---------------
|
||||
* NaN | NaN
|
||||
* Inf | Inf
|
||||
* normal, fits in output | converted (possibly lossy)
|
||||
* normal, does not fit in output | ec is set
|
||||
* subnormal | best effort
|
||||
* -Inf | -Inf
|
||||
*/
|
||||
// clang-format on
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
static_assert(std::is_floating_point<To>::value, "To must be floating");
|
||||
|
||||
// catch the only happy case
|
||||
if (std::isfinite(from)) {
|
||||
if (from >= T::lowest() && from <= T::max()) {
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
// not within range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// nan and inf will be preserved
|
||||
return static_cast<To>(from);
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration cast between integral durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
struct Factor
|
||||
: std::ratio_divide<typename From::period, typename To::period> {};
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// safe conversion to IntermediateRep
|
||||
IntermediateRep count =
|
||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= Factor::num;
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
count /= Factor::den;
|
||||
}
|
||||
// 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};
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration_cast between floating point durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
if (std::isnan(from.count())) {
|
||||
// nan in, gives nan out. easy.
|
||||
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
|
||||
}
|
||||
// maybe we should also check if from is denormal, and decide what to do about
|
||||
// it.
|
||||
|
||||
// +-inf should be preserved.
|
||||
if (std::isinf(from.count())) {
|
||||
return To{from.count()};
|
||||
}
|
||||
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
struct Factor
|
||||
: std::ratio_divide<typename From::period, typename To::period> {};
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// force conversion of From::rep -> IntermediateRep to be safe,
|
||||
// even if it will never happen be narrowing in this context.
|
||||
IntermediateRep count =
|
||||
safe_float_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 = internal::max_value<IntermediateRep>() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= static_cast<IntermediateRep>(Factor::num);
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
|
||||
count /= static_cast<common_t>(Factor::den);
|
||||
}
|
||||
|
||||
// convert to the to type, safely
|
||||
using ToRep = typename To::rep;
|
||||
|
||||
const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return To{tocount};
|
||||
}
|
||||
} // namespace safe_duration_cast
|
||||
#endif
|
||||
|
||||
// Prevents expansion of a preceding token as a function-style macro.
|
||||
// Usage: f FMT_NOMACRO()
|
||||
@ -220,12 +495,12 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
||||
handler.on_text(ptr - 1, ptr);
|
||||
break;
|
||||
case 'n': {
|
||||
const char newline[] = "\n";
|
||||
const Char newline[] = {'\n'};
|
||||
handler.on_text(newline, newline + 1);
|
||||
break;
|
||||
}
|
||||
case 't': {
|
||||
const char tab[] = "\t";
|
||||
const Char tab[] = {'\t'};
|
||||
handler.on_text(tab, tab + 1);
|
||||
break;
|
||||
}
|
||||
@ -403,7 +678,7 @@ inline bool isfinite(T value) {
|
||||
return std::isfinite(value);
|
||||
}
|
||||
|
||||
// Convers 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)>
|
||||
inline int to_nonnegative_int(T value, int upper) {
|
||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
|
||||
@ -421,7 +696,7 @@ inline int to_nonnegative_int(T value, int upper) {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
inline T mod(T x, int y) {
|
||||
return x % y;
|
||||
return x % static_cast<T>(y);
|
||||
}
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
inline T mod(T x, int y) {
|
||||
@ -484,18 +759,30 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
||||
}
|
||||
|
||||
template <typename Rep, typename OutputIt>
|
||||
OutputIt format_chrono_duration_value(OutputIt out, Rep val, int precision) {
|
||||
if (precision >= 0) return format_to(out, "{:.{}f}", val, precision);
|
||||
return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}",
|
||||
template <typename Char, typename Rep, typename OutputIt>
|
||||
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||
const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0};
|
||||
if (precision >= 0) return format_to(out, pr_f, val, precision);
|
||||
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 Period, typename OutputIt>
|
||||
static OutputIt format_chrono_duration_unit(OutputIt out) {
|
||||
if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit);
|
||||
if (Period::den == 1) return format_to(out, "[{}]s", Period::num);
|
||||
return format_to(out, "[{}/{}]s", Period::num, Period::den);
|
||||
template <typename Char, typename Period, typename OutputIt>
|
||||
OutputIt format_duration_unit(OutputIt out) {
|
||||
if (const char* unit = get_units<Period>()) {
|
||||
string_view s(unit);
|
||||
if (const_check(std::is_same<Char, wchar_t>())) {
|
||||
utf8_to_utf16 u(s);
|
||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||
}
|
||||
return std::copy(s.begin(), s.end(), out);
|
||||
}
|
||||
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
|
||||
if (Period::den == 1) return format_to(out, num_f, Period::num);
|
||||
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
|
||||
return format_to(out, num_def_f, Period::num, Period::den);
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename OutputIt, typename Rep,
|
||||
@ -518,7 +805,10 @@ struct chrono_formatter {
|
||||
|
||||
explicit chrono_formatter(FormatContext& ctx, OutputIt o,
|
||||
std::chrono::duration<Rep, Period> d)
|
||||
: context(ctx), out(o), val(d.count()), negative(false) {
|
||||
: context(ctx),
|
||||
out(o),
|
||||
val(static_cast<rep>(d.count())),
|
||||
negative(false) {
|
||||
if (d.count() < 0) {
|
||||
val = 0 - val;
|
||||
negative = true;
|
||||
@ -582,8 +872,8 @@ struct chrono_formatter {
|
||||
void write(Rep value, int width) {
|
||||
write_sign();
|
||||
if (isnan(value)) return write_nan();
|
||||
uint32_or_64_t<int> n = to_unsigned(
|
||||
to_nonnegative_int(value, (std::numeric_limits<int>::max)()));
|
||||
uint32_or_64_or_128_t<int> n =
|
||||
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
||||
int num_digits = internal::count_digits(n);
|
||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
||||
out = format_decimal<char_type>(out, n, num_digits);
|
||||
@ -593,13 +883,13 @@ struct chrono_formatter {
|
||||
void write_pinf() { std::copy_n("inf", 3, out); }
|
||||
void write_ninf() { std::copy_n("-inf", 4, out); }
|
||||
|
||||
void format_localized(const tm& time, const char* format) {
|
||||
void format_localized(const tm& time, char format, char modifier = 0) {
|
||||
if (isnan(val)) return write_nan();
|
||||
auto locale = context.locale().template get<std::locale>();
|
||||
auto& facet = std::use_facet<std::time_put<char_type>>(locale);
|
||||
std::basic_ostringstream<char_type> os;
|
||||
os.imbue(locale);
|
||||
facet.put(os, os, ' ', &time, format, format + std::strlen(format));
|
||||
facet.put(os, os, ' ', &time, format, modifier);
|
||||
auto str = os.str();
|
||||
std::copy(str.begin(), str.end(), out);
|
||||
}
|
||||
@ -629,7 +919,7 @@ struct chrono_formatter {
|
||||
if (ns == numeric_system::standard) return write(hour(), 2);
|
||||
auto time = tm();
|
||||
time.tm_hour = to_nonnegative_int(hour(), 24);
|
||||
format_localized(time, "%OH");
|
||||
format_localized(time, 'H', 'O');
|
||||
}
|
||||
|
||||
void on_12_hour(numeric_system ns) {
|
||||
@ -638,7 +928,7 @@ struct chrono_formatter {
|
||||
if (ns == numeric_system::standard) return write(hour12(), 2);
|
||||
auto time = tm();
|
||||
time.tm_hour = to_nonnegative_int(hour12(), 12);
|
||||
format_localized(time, "%OI");
|
||||
format_localized(time, 'I', 'O');
|
||||
}
|
||||
|
||||
void on_minute(numeric_system ns) {
|
||||
@ -647,7 +937,7 @@ struct chrono_formatter {
|
||||
if (ns == numeric_system::standard) return write(minute(), 2);
|
||||
auto time = tm();
|
||||
time.tm_min = to_nonnegative_int(minute(), 60);
|
||||
format_localized(time, "%OM");
|
||||
format_localized(time, 'M', 'O');
|
||||
}
|
||||
|
||||
void on_second(numeric_system ns) {
|
||||
@ -672,13 +962,12 @@ struct chrono_formatter {
|
||||
}
|
||||
auto time = tm();
|
||||
time.tm_sec = to_nonnegative_int(second(), 60);
|
||||
format_localized(time, "%OS");
|
||||
format_localized(time, 'S', 'O');
|
||||
}
|
||||
|
||||
void on_12_hour_time() {
|
||||
if (handle_nan_inf()) return;
|
||||
|
||||
format_localized(time(), "%r");
|
||||
format_localized(time(), 'r');
|
||||
}
|
||||
|
||||
void on_24_hour_time() {
|
||||
@ -702,16 +991,18 @@ struct chrono_formatter {
|
||||
|
||||
void on_am_pm() {
|
||||
if (handle_nan_inf()) return;
|
||||
format_localized(time(), "%p");
|
||||
format_localized(time(), 'p');
|
||||
}
|
||||
|
||||
void on_duration_value() {
|
||||
if (handle_nan_inf()) return;
|
||||
write_sign();
|
||||
out = format_chrono_duration_value(out, val, precision);
|
||||
out = format_duration_value<char_type>(out, val, precision);
|
||||
}
|
||||
|
||||
void on_duration_unit() { out = format_chrono_duration_unit<Period>(out); }
|
||||
void on_duration_unit() {
|
||||
out = format_duration_unit<char_type, Period>(out);
|
||||
}
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
@ -728,7 +1019,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
|
||||
struct spec_handler {
|
||||
formatter& f;
|
||||
basic_parse_context<Char>& context;
|
||||
basic_format_parse_context<Char>& context;
|
||||
basic_string_view<Char> format_str;
|
||||
|
||||
template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
|
||||
@ -738,8 +1029,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
|
||||
context.check_arg_id(arg_id);
|
||||
const auto str_val = internal::string_view_metadata(format_str, arg_id);
|
||||
return arg_ref_type(str_val);
|
||||
return arg_ref_type(arg_id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
|
||||
@ -747,10 +1037,10 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
}
|
||||
|
||||
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
||||
void on_fill(Char fill) { f.specs.fill[0] = fill; }
|
||||
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
|
||||
void on_align(align_t align) { f.specs.align = align; }
|
||||
void on_width(unsigned width) { f.specs.width = width; }
|
||||
void on_precision(unsigned precision) { f.precision = precision; }
|
||||
void on_width(int width) { f.specs.width = width; }
|
||||
void on_precision(int _precision) { f.precision = _precision; }
|
||||
void end_precision() {}
|
||||
|
||||
template <typename Id> void on_dynamic_width(Id arg_id) {
|
||||
@ -762,13 +1052,13 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
}
|
||||
};
|
||||
|
||||
using iterator = typename basic_parse_context<Char>::iterator;
|
||||
using iterator = typename basic_format_parse_context<Char>::iterator;
|
||||
struct parse_range {
|
||||
iterator begin;
|
||||
iterator end;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR parse_range do_parse(basic_parse_context<Char>& ctx) {
|
||||
FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) {
|
||||
auto begin = ctx.begin(), end = ctx.end();
|
||||
if (begin == end || *begin == '}') return {begin, begin};
|
||||
spec_handler handler{*this, ctx, format_str};
|
||||
@ -789,7 +1079,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
public:
|
||||
formatter() : precision(-1) {}
|
||||
|
||||
FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
auto range = do_parse(ctx);
|
||||
format_str = basic_string_view<Char>(
|
||||
@ -806,13 +1096,13 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||
auto out = std::back_inserter(buf);
|
||||
using range = internal::output_range<decltype(ctx.out()), Char>;
|
||||
internal::basic_writer<range> w(range(ctx.out()));
|
||||
internal::handle_dynamic_spec<internal::width_checker>(
|
||||
specs.width, width_ref, ctx, format_str.begin());
|
||||
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
|
||||
width_ref, ctx);
|
||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
||||
precision, precision_ref, ctx, format_str.begin());
|
||||
precision, precision_ref, ctx);
|
||||
if (begin == end || *begin == '}') {
|
||||
out = internal::format_chrono_duration_value(out, d.count(), precision);
|
||||
internal::format_chrono_duration_unit<Period>(out);
|
||||
out = internal::format_duration_value<Char>(out, d.count(), precision);
|
||||
internal::format_duration_unit<Char, Period>(out);
|
||||
} else {
|
||||
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||
ctx, out, d);
|
||||
|
@ -299,15 +299,15 @@ class text_style {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
assert(has_foreground() && "no foreground specified for this style");
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
|
||||
assert(has_background() && "no background specified for this style");
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
assert(has_emphasis() && "no emphasis specified for this style");
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
@ -412,7 +412,7 @@ template <typename Char> struct ansi_color_escape {
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::strlen(buffer);
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -470,58 +470,39 @@ inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
std::basic_string<Char> vformat(const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char> > args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
ansi_color_escape<Char> escape = make_emphasis<Char>(ts.get_emphasis());
|
||||
buffer.append(escape.begin(), escape.end());
|
||||
auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
ansi_color_escape<Char> escape =
|
||||
make_foreground_color<Char>(ts.get_foreground());
|
||||
buffer.append(escape.begin(), escape.end());
|
||||
auto foreground =
|
||||
internal::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
ansi_color_escape<Char> escape =
|
||||
make_background_color<Char>(ts.get_background());
|
||||
buffer.append(escape.begin(), escape.end());
|
||||
auto background =
|
||||
internal::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
internal::vformat_to(buffer, format_str, args);
|
||||
if (has_style) {
|
||||
reset_color<Char>(buffer);
|
||||
}
|
||||
return fmt::to_string(buffer);
|
||||
internal::vformat_to(buf, format_str, args);
|
||||
if (has_style) internal::reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
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,
|
||||
basic_format_args<buffer_context<Char> > args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
internal::fputs<Char>(internal::make_emphasis<Char>(ts.get_emphasis()), f);
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
internal::fputs<Char>(
|
||||
internal::make_foreground_color<Char>(ts.get_foreground()), f);
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
internal::fputs<Char>(
|
||||
internal::make_background_color<Char>(ts.get_background()), f);
|
||||
}
|
||||
vprint(f, format, args);
|
||||
if (has_style) {
|
||||
internal::reset_color<Char>(f);
|
||||
}
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
internal::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -536,7 +517,7 @@ template <typename S, typename... Args,
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
internal::check_format_string<Args...>(format_str);
|
||||
using context = buffer_context<char_t<S> >;
|
||||
using context = buffer_context<char_t<S>>;
|
||||
format_arg_store<context, Args...> as{args...};
|
||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
||||
}
|
||||
@ -554,11 +535,13 @@ void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S> >
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<Char> > args) {
|
||||
return internal::vformat(ts, to_string_view(format_str), args);
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
internal::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -573,11 +556,11 @@ inline std::basic_string<Char> vformat(
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
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,
|
||||
const Args&... args) {
|
||||
return internal::vformat(ts, to_string_view(format_str),
|
||||
{internal::make_args_checked(format_str, args...)});
|
||||
return vformat(ts, to_string_view(format_str),
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
@ -9,255 +9,50 @@
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// Part of a compiled format string. It can be either literal text or a
|
||||
// replacement field.
|
||||
template <typename Char> struct format_part {
|
||||
public:
|
||||
struct named_argument_id {
|
||||
FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id)
|
||||
: id(id) {}
|
||||
internal::string_view_metadata id;
|
||||
enum class kind { arg_index, arg_name, text, replacement };
|
||||
|
||||
struct replacement {
|
||||
arg_ref<Char> arg_id;
|
||||
dynamic_format_specs<Char> specs;
|
||||
};
|
||||
|
||||
struct argument_id {
|
||||
FMT_CONSTEXPR argument_id() : argument_id(0u) {}
|
||||
|
||||
FMT_CONSTEXPR argument_id(unsigned id)
|
||||
: which(which_arg_id::index), val(id) {}
|
||||
|
||||
FMT_CONSTEXPR argument_id(internal::string_view_metadata id)
|
||||
: which(which_arg_id::named_index), val(id) {}
|
||||
|
||||
enum class which_arg_id { index, named_index };
|
||||
|
||||
which_arg_id which;
|
||||
|
||||
union value {
|
||||
FMT_CONSTEXPR value() : index(0u) {}
|
||||
FMT_CONSTEXPR value(unsigned id) : index(id) {}
|
||||
FMT_CONSTEXPR value(internal::string_view_metadata id)
|
||||
: named_index(id) {}
|
||||
|
||||
unsigned index;
|
||||
internal::string_view_metadata named_index;
|
||||
} val;
|
||||
};
|
||||
|
||||
struct specification {
|
||||
FMT_CONSTEXPR specification() : arg_id(0u) {}
|
||||
FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {}
|
||||
|
||||
FMT_CONSTEXPR specification(internal::string_view_metadata id)
|
||||
: arg_id(id) {}
|
||||
|
||||
argument_id arg_id;
|
||||
internal::dynamic_format_specs<Char> parsed_specs;
|
||||
};
|
||||
|
||||
FMT_CONSTEXPR format_part()
|
||||
: which(kind::argument_id), end_of_argument_id(0u), val(0u) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(internal::string_view_metadata text)
|
||||
: which(kind::text), end_of_argument_id(0u), val(text) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(unsigned id)
|
||||
: which(kind::argument_id), end_of_argument_id(0u), val(id) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(named_argument_id arg_id)
|
||||
: which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {}
|
||||
|
||||
FMT_CONSTEXPR format_part(specification spec)
|
||||
: which(kind::specification), end_of_argument_id(0u), val(spec) {}
|
||||
|
||||
enum class kind { argument_id, named_argument_id, text, specification };
|
||||
|
||||
kind which;
|
||||
std::size_t end_of_argument_id;
|
||||
kind part_kind;
|
||||
union value {
|
||||
FMT_CONSTEXPR value() : arg_id(0u) {}
|
||||
FMT_CONSTEXPR value(unsigned id) : arg_id(id) {}
|
||||
FMT_CONSTEXPR value(named_argument_id named_id)
|
||||
: named_arg_id(named_id.id) {}
|
||||
FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {}
|
||||
FMT_CONSTEXPR value(specification s) : spec(s) {}
|
||||
unsigned arg_id;
|
||||
internal::string_view_metadata named_arg_id;
|
||||
internal::string_view_metadata text;
|
||||
specification spec;
|
||||
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;
|
||||
|
||||
template <typename Char, typename PartsContainer>
|
||||
class format_preparation_handler : public internal::error_handler {
|
||||
private:
|
||||
using part = format_part<Char>;
|
||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||
: part_kind(k), val(v) {}
|
||||
|
||||
public:
|
||||
using iterator = typename basic_string_view<Char>::iterator;
|
||||
|
||||
FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format,
|
||||
PartsContainer& parts)
|
||||
: parts_(parts), format_(format), parse_context_(format) {}
|
||||
|
||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||
if (begin == end) return;
|
||||
const auto offset = begin - format_.data();
|
||||
const auto size = end - begin;
|
||||
parts_.push_back(part(string_view_metadata(offset, size)));
|
||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
||||
return format_part(kind::arg_index, index);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
parts_.push_back(part(parse_context_.next_arg_id()));
|
||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
||||
return format_part(kind::arg_name, name);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
parts_.push_back(part(id));
|
||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
||||
return format_part(kind::text, text);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
const auto view = string_view_metadata(format_, id);
|
||||
const auto arg_id = typename part::named_argument_id(view);
|
||||
parts_.push_back(part(arg_id));
|
||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||
return format_part(kind::replacement, repl);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
parts_.back().end_of_argument_id = ptr - format_.begin();
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
const auto specs_offset = to_unsigned(begin - format_.begin());
|
||||
|
||||
using parse_context = basic_parse_context<Char>;
|
||||
internal::dynamic_format_specs<Char> parsed_specs;
|
||||
dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_);
|
||||
begin = parse_format_specs(begin, end, handler);
|
||||
|
||||
if (*begin != '}') on_error("missing '}' in format string");
|
||||
|
||||
auto& last_part = parts_.back();
|
||||
auto specs = last_part.which == part::kind::argument_id
|
||||
? typename part::specification(last_part.val.arg_id)
|
||||
: typename part::specification(last_part.val.named_arg_id);
|
||||
specs.parsed_specs = parsed_specs;
|
||||
last_part = part(specs);
|
||||
last_part.end_of_argument_id = specs_offset;
|
||||
return begin;
|
||||
}
|
||||
|
||||
private:
|
||||
PartsContainer& parts_;
|
||||
basic_string_view<Char> format_;
|
||||
basic_parse_context<Char> parse_context_;
|
||||
};
|
||||
|
||||
template <typename Format, typename PreparedPartsProvider, typename... Args>
|
||||
class prepared_format {
|
||||
public:
|
||||
using char_type = char_t<Format>;
|
||||
using format_part_t = format_part<char_type>;
|
||||
|
||||
constexpr prepared_format(Format f)
|
||||
: format_(std::move(f)), parts_provider_(to_string_view(format_)) {}
|
||||
|
||||
prepared_format() = delete;
|
||||
|
||||
using context = buffer_context<char_type>;
|
||||
|
||||
template <typename Range, typename Context>
|
||||
auto vformat_to(Range out, basic_format_args<Context> args) const ->
|
||||
typename Context::iterator {
|
||||
const auto format_view = internal::to_string_view(format_);
|
||||
basic_parse_context<char_type> parse_ctx(format_view);
|
||||
Context ctx(out.begin(), args);
|
||||
|
||||
const auto& parts = parts_provider_.parts();
|
||||
for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) {
|
||||
const auto& part = *part_it;
|
||||
const auto& value = part.val;
|
||||
|
||||
switch (part.which) {
|
||||
case format_part_t::kind::text: {
|
||||
const auto text = value.text.to_view(format_view.data());
|
||||
auto output = ctx.out();
|
||||
auto&& it = internal::reserve(output, text.size());
|
||||
it = std::copy_n(text.begin(), text.size(), it);
|
||||
ctx.advance_to(output);
|
||||
} break;
|
||||
|
||||
case format_part_t::kind::argument_id: {
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
format_arg<Range>(parse_ctx, ctx, value.arg_id);
|
||||
} break;
|
||||
|
||||
case format_part_t::kind::named_argument_id: {
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
const auto named_arg_id =
|
||||
value.named_arg_id.to_view(format_view.data());
|
||||
format_arg<Range>(parse_ctx, ctx, named_arg_id);
|
||||
} break;
|
||||
case format_part_t::kind::specification: {
|
||||
const auto& arg_id_value = value.spec.arg_id.val;
|
||||
const auto arg = value.spec.arg_id.which ==
|
||||
format_part_t::argument_id::which_arg_id::index
|
||||
? ctx.arg(arg_id_value.index)
|
||||
: ctx.arg(arg_id_value.named_index.to_view(
|
||||
to_string_view(format_).data()));
|
||||
|
||||
auto specs = value.spec.parsed_specs;
|
||||
|
||||
handle_dynamic_spec<internal::width_checker>(
|
||||
specs.width, specs.width_ref, ctx, format_view.begin());
|
||||
handle_dynamic_spec<internal::precision_checker>(
|
||||
specs.precision, specs.precision_ref, ctx, format_view.begin());
|
||||
|
||||
check_prepared_specs(specs, arg.type());
|
||||
advance_parse_context_to_specification(parse_ctx, part);
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
private:
|
||||
void advance_parse_context_to_specification(
|
||||
basic_parse_context<char_type>& parse_ctx,
|
||||
const format_part_t& part) const {
|
||||
const auto view = to_string_view(format_);
|
||||
const auto specification_begin = view.data() + part.end_of_argument_id;
|
||||
advance_to(parse_ctx, specification_begin);
|
||||
}
|
||||
|
||||
template <typename Range, typename Context, typename Id>
|
||||
void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx,
|
||||
Id arg_id) const {
|
||||
parse_ctx.check_arg_id(arg_id);
|
||||
const auto stopped_at =
|
||||
visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id));
|
||||
ctx.advance_to(stopped_at);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void check_prepared_specs(const basic_format_specs<Char>& specs,
|
||||
internal::type arg_type) const {
|
||||
internal::error_handler h;
|
||||
numeric_specs_checker<internal::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();
|
||||
}
|
||||
|
||||
private:
|
||||
Format format_;
|
||||
PreparedPartsProvider parts_provider_;
|
||||
};
|
||||
|
||||
template <typename Char> struct part_counter {
|
||||
@ -268,7 +63,7 @@ template <typename Char> struct part_counter {
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
|
||||
@ -276,13 +71,13 @@ template <typename Char> struct part_counter {
|
||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||
const Char* end) {
|
||||
// Find the matching brace.
|
||||
unsigned braces_counter = 0;
|
||||
unsigned brace_counter = 0;
|
||||
for (; begin != end; ++begin) {
|
||||
if (*begin == '{') {
|
||||
++braces_counter;
|
||||
++brace_counter;
|
||||
} else if (*begin == '}') {
|
||||
if (braces_counter == 0u) break;
|
||||
--braces_counter;
|
||||
if (brace_counter == 0u) break;
|
||||
--brace_counter;
|
||||
}
|
||||
}
|
||||
return begin;
|
||||
@ -291,156 +86,493 @@ template <typename Char> struct part_counter {
|
||||
FMT_CONSTEXPR void on_error(const char*) {}
|
||||
};
|
||||
|
||||
template <typename Format> class compiletime_prepared_parts_type_provider {
|
||||
private:
|
||||
using char_type = char_t<Format>;
|
||||
// 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;
|
||||
}
|
||||
|
||||
static FMT_CONSTEXPR unsigned count_parts() {
|
||||
FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{});
|
||||
part_counter<char_type> counter;
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, 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)}));
|
||||
}
|
||||
|
||||
// Workaround for old compilers. Compiletime parts preparation will not be
|
||||
// performed with them anyway.
|
||||
FMT_CONSTEXPR void on_arg_id() {
|
||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(int id) {
|
||||
parse_context_.check_arg_id(id);
|
||||
part_ = part::make_arg_index(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||
part_ = part::make_arg_name(id);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
||||
part_.arg_id_end = ptr;
|
||||
handler_(part_);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR const Char* on_format_specs(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 Range, typename Context, typename Id>
|
||||
void format_arg(
|
||||
basic_format_parse_context<typename Range::value_type>& parse_ctx,
|
||||
Context& ctx, Id arg_id) {
|
||||
ctx.advance_to(
|
||||
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
|
||||
}
|
||||
|
||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
||||
namespace cf {
|
||||
template <typename Context, typename Range, typename CompiledFormat>
|
||||
auto vformat_to(Range 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.begin(), 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);
|
||||
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
|
||||
break;
|
||||
|
||||
case format_part_t::kind::arg_name:
|
||||
advance_to(parse_ctx, part.arg_id_end);
|
||||
internal::format_arg<Range>(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<Range>(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<internal::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 number_of_format_parts =
|
||||
compiletime_prepared_parts_type_provider::count_parts();
|
||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
||||
#else
|
||||
static const unsigned number_of_format_parts = 0u;
|
||||
static const unsigned num_format_parts = 1;
|
||||
#endif
|
||||
|
||||
public:
|
||||
template <unsigned N> struct format_parts_array {
|
||||
using value_type = format_part<char_type>;
|
||||
using parts_container = format_part<char_type>[num_format_parts];
|
||||
|
||||
FMT_CONSTEXPR format_parts_array() : arr{} {}
|
||||
|
||||
FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; }
|
||||
|
||||
FMT_CONSTEXPR const value_type* begin() const { return arr; }
|
||||
FMT_CONSTEXPR const value_type* end() const { return begin() + N; }
|
||||
|
||||
private:
|
||||
value_type arr[N];
|
||||
};
|
||||
|
||||
struct empty {
|
||||
// Parts preparator will search for it
|
||||
using value_type = format_part<char_type>;
|
||||
};
|
||||
|
||||
using type = conditional_t<number_of_format_parts != 0,
|
||||
format_parts_array<number_of_format_parts>, empty>;
|
||||
};
|
||||
|
||||
template <typename Parts> class compiletime_prepared_parts_collector {
|
||||
private:
|
||||
using format_part = typename Parts::value_type;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts)
|
||||
: parts_{parts}, counter_{0u} {}
|
||||
|
||||
FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; }
|
||||
|
||||
FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; }
|
||||
|
||||
private:
|
||||
Parts& parts_;
|
||||
unsigned counter_;
|
||||
};
|
||||
|
||||
template <typename PartsContainer, typename Char>
|
||||
FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) {
|
||||
PartsContainer parts;
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/false>(
|
||||
format, format_preparation_handler<Char, PartsContainer>(format, parts));
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename PartsContainer, typename Char>
|
||||
FMT_CONSTEXPR PartsContainer
|
||||
prepare_compiletime_parts(basic_string_view<Char> format) {
|
||||
using collector = compiletime_prepared_parts_collector<PartsContainer>;
|
||||
|
||||
PartsContainer parts;
|
||||
collector c(parts);
|
||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(
|
||||
format, format_preparation_handler<Char, collector>(format, c));
|
||||
return parts;
|
||||
}
|
||||
|
||||
template <typename PartsContainer> class runtime_parts_provider {
|
||||
public:
|
||||
runtime_parts_provider() = delete;
|
||||
template <typename Char>
|
||||
runtime_parts_provider(basic_string_view<Char> format)
|
||||
: parts_(prepare_parts<PartsContainer>(format)) {}
|
||||
|
||||
const PartsContainer& parts() const { return parts_; }
|
||||
|
||||
private:
|
||||
PartsContainer parts_;
|
||||
};
|
||||
|
||||
template <typename Format, typename PartsContainer>
|
||||
struct compiletime_parts_provider {
|
||||
compiletime_parts_provider() = delete;
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {}
|
||||
|
||||
const PartsContainer& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts =
|
||||
prepare_compiletime_parts<PartsContainer>(
|
||||
internal::to_string_view(Format{}));
|
||||
|
||||
return prepared_parts;
|
||||
const parts_container& parts() const {
|
||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
||||
compile_to_parts<char_type, num_format_parts>(
|
||||
internal::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 Range, typename CompiledFormat>
|
||||
friend auto cf::vformat_to(Range 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
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename T> struct is_compiled_format : std::false_type {};
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&...) const {
|
||||
// TODO: reserve
|
||||
return copy_str<Char>(data.begin(), data.end(), out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename T,
|
||||
std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
||||
OutputIt format_default(OutputIt out, T value) {
|
||||
// TODO: reserve
|
||||
format_int fi(value);
|
||||
return std::copy(fi.data(), fi.data() + fi.size(), out);
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, double value) {
|
||||
writer w(out);
|
||||
w.write(value);
|
||||
return w.out();
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, Char value) {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt>
|
||||
OutputIt format_default(OutputIt out, const Char* value) {
|
||||
auto length = std::char_traits<Char>::length(value);
|
||||
return copy_str<Char>(value, value + length, out);
|
||||
}
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
// This ensures that the argument type is convertile to `const T&`.
|
||||
const T& arg = get<N>(args...);
|
||||
return format_default<Char>(out, arg);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS != to_string_view(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr basic_string_view<char_type> str = format_str;
|
||||
if constexpr (str[POS] == '{') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}') {
|
||||
using type = get_type<ID, Args>;
|
||||
if constexpr (std::is_same<type, int>::value) {
|
||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
||||
format_str);
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else {
|
||||
return unknown_format();
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
} // namespace internal
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
# ifdef __cpp_if_constexpr
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format<
|
||||
S,
|
||||
internal::compiletime_parts_provider<
|
||||
S,
|
||||
typename internal::compiletime_prepared_parts_type_provider<S>::type>,
|
||||
Args...> {
|
||||
return format_str;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename... Args, typename Char, size_t N>
|
||||
auto compile(const Char (&format_str)[N]) -> internal::prepared_format<
|
||||
std::basic_string<Char>,
|
||||
internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>,
|
||||
Args...> {
|
||||
return std::basic_string<Char>(format_str, N - 1);
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
||||
if constexpr (str.size() == 0) {
|
||||
return internal::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
||||
internal::unknown_format>()) {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type>
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = internal::buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
cf.template vformat_to<range, context>(range(buffer),
|
||||
{make_format_args<context>(args...)});
|
||||
cf.format(std::back_inserter(buffer), args...);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args>
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
# else
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
||||
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
|
||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
||||
}
|
||||
# endif // __cpp_if_constexpr
|
||||
#endif // FMT_USE_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])
|
||||
-> internal::compiled_format<const Char*, Args...> {
|
||||
return internal::compiled_format<const Char*, Args...>(
|
||||
basic_string_view<Char>(format_str, N - 1));
|
||||
}
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
using range = buffer_range<Char>;
|
||||
using context = buffer_context<Char>;
|
||||
internal::cf::vformat_to<context>(range(buffer), cf,
|
||||
make_format_args<context>(args...));
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
||||
CompiledFormat>::value)>
|
||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
using char_type = typename CompiledFormat::char_type;
|
||||
using range = internal::output_range<OutputIt, char_type>;
|
||||
using context = format_context_t<OutputIt, char_type>;
|
||||
return cf.template vformat_to<range, context>(
|
||||
range(out), {make_format_args<context>(args...)});
|
||||
return internal::cf::vformat_to<context>(range(out), cf,
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
@ -455,10 +587,7 @@ format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
|
||||
template <typename CompiledFormat, typename... Args>
|
||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
||||
return fmt::format_to(
|
||||
internal::counting_iterator<typename CompiledFormat::char_type>(),
|
||||
cf, args...)
|
||||
.count();
|
||||
return format_to(internal::counting_iterator(), cf, args...).count();
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1698
include/fmt/format.h
1698
include/fmt/format.h
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@
|
||||
#define FMT_LOCALE_H_
|
||||
|
||||
#include <locale>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
@ -18,16 +19,16 @@ 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<Char>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
using range = buffer_range<Char>;
|
||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
|
||||
internal::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<Char>> args) {
|
||||
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;
|
||||
internal::vformat_to(loc, buffer, format_str, args);
|
||||
return fmt::to_string(buffer);
|
||||
@ -37,7 +38,7 @@ std::basic_string<Char> vformat(const std::locale& loc,
|
||||
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<Char>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
return internal::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
@ -46,15 +47,15 @@ inline std::basic_string<Char> format(const std::locale& loc,
|
||||
const S& format_str, Args&&... args) {
|
||||
return internal::vformat(
|
||||
loc, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = enable_if_t<
|
||||
internal::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<OutputIt, Char> args) {
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const std::locale& loc, const S& format_str,
|
||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
||||
using range = internal::output_range<OutputIt, Char>;
|
||||
return vformat_to<arg_formatter<range>>(
|
||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
|
||||
|
392
include/fmt/os.h
Normal file
392
include/fmt/os.h
Normal file
@ -0,0 +1,392 @@
|
||||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef 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 <clocale> // for locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // for strtod_l
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if FMT_HAS_INCLUDE("fcntl.h") && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace internal {
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(wstring_view s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(wstring_view s);
|
||||
};
|
||||
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
} // namespace internal
|
||||
|
||||
/** A Windows error. */
|
||||
class windows_error : public system_error {
|
||||
private:
|
||||
FMT_API void init(int error_code, string_view format_str, format_args args);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`fmt::windows_error` object with the description
|
||||
of the form
|
||||
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
|
||||
**Example**::
|
||||
|
||||
// This throws a windows_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
windows_error(int error_code, string_view message, const Args&... args) {
|
||||
init(error_code, message, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code,
|
||||
string_view message) FMT_NOEXCEPT;
|
||||
#endif // _WIN32
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) FMT_NOEXCEPT {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API std::size_t read(void* buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API std::size_t write(const void* buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
@ -46,9 +46,13 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
struct null;
|
||||
// Hide all operator<< from std::basic_ostream<Char>.
|
||||
void operator<<(null);
|
||||
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);
|
||||
};
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
@ -56,9 +60,9 @@ template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static decltype((void)(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
std::true_type())
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
@ -75,8 +79,7 @@ void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size =
|
||||
to_unsigned((std::numeric_limits<std::streamsize>::max)());
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
@ -86,9 +89,13 @@ void write(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value) {
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
output << value;
|
||||
buf.resize(buf.size());
|
||||
@ -101,7 +108,7 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
template <typename Context>
|
||||
auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value);
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
@ -110,7 +117,7 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<Char>> args) {
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
internal::vformat_to(buffer, format_str, args);
|
||||
internal::write(os, buffer);
|
||||
@ -129,7 +136,7 @@ template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
{internal::make_args_checked<Args...>(format_str, args...)});
|
||||
internal::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
@ -1,311 +1,2 @@
|
||||
// A C++ interface to POSIX functions.
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_POSIX_H_
|
||||
#define FMT_POSIX_H_
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/.
|
||||
# undef __STRICT_ANSI__
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h> // for O_RDONLY
|
||||
#include <locale.h> // for locale_t
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h> // for strtod_l
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
result = (expression); \
|
||||
} while (result == error_result && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
// An error code.
|
||||
class error_code {
|
||||
private:
|
||||
int value_;
|
||||
|
||||
public:
|
||||
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
|
||||
|
||||
int get() const FMT_NOEXCEPT { return value_; }
|
||||
};
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
|
||||
private:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
private:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
public:
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
file& operator=(file&& other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API std::size_t read(void* buffer, std::size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API std::size_t write(const void* buffer, std::size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class Locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
|
||||
static locale_t newlocale(int category_mask, const char* locale, locale_t) {
|
||||
return _create_locale(category_mask, locale);
|
||||
}
|
||||
|
||||
static void freelocale(locale_t locale) { _free_locale(locale); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
return _strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
Locale(const Locale&) = delete;
|
||||
void operator=(const Locale&) = delete;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
|
||||
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) {
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~Locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
#endif // FMT_LOCALE
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_POSIX_H_
|
||||
#include "os.h"
|
||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
@ -1,4 +1,4 @@
|
||||
// Formatting library for C++
|
||||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@ -8,7 +8,7 @@
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::fill_n
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
|
||||
#include "ostream.h"
|
||||
@ -16,15 +16,11 @@
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
|
||||
// A helper function to suppress bogus "conditional expression is constant"
|
||||
// warnings.
|
||||
template <typename T> inline T const_check(T value) { return value; }
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = std::numeric_limits<int>::max();
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
@ -32,8 +28,8 @@ template <bool IsSigned> struct int_checker {
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= std::numeric_limits<int>::min() &&
|
||||
value <= std::numeric_limits<int>::max();
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
@ -158,12 +154,12 @@ template <typename Char> class printf_width_handler {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_t<T>>(value);
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (internal::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = std::numeric_limits<int>::max();
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
@ -235,7 +231,7 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
|
||||
iterator operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
||||
// use std::is_same instead.
|
||||
@ -307,6 +303,8 @@ class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
||||
};
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
@ -324,6 +322,7 @@ 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>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
@ -332,17 +331,17 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
basic_parse_context<Char> parse_ctx_;
|
||||
basic_format_parse_context<Char> 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 equal
|
||||
// to the maximum unsigned value, the next argument.
|
||||
format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max());
|
||||
// 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.
|
||||
unsigned parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
|
||||
public:
|
||||
/**
|
||||
@ -359,17 +358,18 @@ template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
format_arg arg(unsigned id) const { return args_.get(id); }
|
||||
internal::locale_ref locale() { return {}; }
|
||||
|
||||
basic_parse_context<Char>& parse_context() { return parse_ctx_; }
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
basic_format_parse_context<Char>& 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<internal::buffer_range<Char>>>
|
||||
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
@ -402,8 +402,8 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) {
|
||||
if (arg_index == std::numeric_limits<unsigned>::max())
|
||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
@ -411,15 +411,16 @@ basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) {
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
unsigned basic_printf_context<OutputIt, Char>::parse_header(
|
||||
const Char*& it, const Char* end, format_specs& specs) {
|
||||
unsigned arg_index = std::numeric_limits<unsigned>::max();
|
||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
const Char* end,
|
||||
format_specs& specs) {
|
||||
int arg_index = -1;
|
||||
char_type c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
internal::error_handler eh;
|
||||
unsigned value = parse_nonnegative_int(it, end, eh);
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
@ -441,8 +442,8 @@ unsigned basic_printf_context<OutputIt, Char>::parse_header(
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = visit_format_arg(
|
||||
internal::printf_width_handler<char_type>(specs), get_arg());
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
internal::printf_width_handler<char_type>(specs), get_arg()));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
@ -469,7 +470,8 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
unsigned arg_index = parse_header(it, end, specs);
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument index out of range");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
@ -477,11 +479,11 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
internal::error_handler eh;
|
||||
specs.precision = static_cast<int>(parse_nonnegative_int(it, end, eh));
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision =
|
||||
visit_format_arg(internal::printf_precision_handler(), get_arg());
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(internal::printf_precision_handler(), get_arg()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
@ -600,7 +602,8 @@ inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vsprintf(
|
||||
const S& format, basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
return to_string(buffer);
|
||||
@ -619,12 +622,13 @@ template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(format), {make_format_args<context>(args...)});
|
||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
inline int vfprintf(
|
||||
std::FILE* f, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
std::size_t size = buffer.size();
|
||||
@ -647,12 +651,13 @@ template <typename S, typename... Args,
|
||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(format),
|
||||
{make_format_args<context>(args...)});
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vprintf(const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
inline int vprintf(
|
||||
const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
return vfprintf(stdout, to_string_view(format), args);
|
||||
}
|
||||
|
||||
@ -670,12 +675,13 @@ template <typename S, typename... Args,
|
||||
inline int printf(const S& format_str, const Args&... args) {
|
||||
using context = basic_printf_context_t<char_t<S>>;
|
||||
return vprintf(to_string_view(format_str),
|
||||
{make_format_args<context>(args...)});
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline int vfprintf(std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<Char>> args) {
|
||||
inline int vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& format,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
printf(buffer, to_string_view(format), args);
|
||||
internal::write(os, buffer);
|
||||
@ -686,9 +692,9 @@ inline int vfprintf(std::basic_ostream<Char>& os, const S& format,
|
||||
template <typename ArgFormatter, typename Char,
|
||||
typename Context =
|
||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
||||
typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<Context> args) {
|
||||
typename ArgFormatter::iterator vprintf(
|
||||
internal::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;
|
||||
@ -708,7 +714,7 @@ 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...)});
|
||||
make_format_args<context>(args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
|
@ -12,7 +12,9 @@
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// output only up to N items from the range.
|
||||
@ -104,10 +106,7 @@ struct is_range_<
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype(std::tuple_size<U>::value,
|
||||
(void)std::declval<typename std::tuple_element<0, U>::type>(),
|
||||
int());
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
@ -246,7 +245,8 @@ template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
internal::is_range_<T>::value &&
|
||||
!internal::is_like_std_string<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value;
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<internal::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename RangeT, typename Char>
|
||||
@ -283,6 +283,105 @@ struct formatter<RangeT, Char,
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple{t}, sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
||||
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
typename FormatContext::iterator format(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
internal::index_sequence<N...>) {
|
||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
typename FormatContext::iterator format_args(
|
||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = ctx.out();
|
||||
out = base{}.format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
||||
string_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
||||
wstring_view sep) {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
|
||||
std::initializer_list<T> list, string_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
|
||||
std::initializer_list<T> list, wstring_view sep) {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
||||
|
@ -1,293 +0,0 @@
|
||||
/*
|
||||
* For conversion between std::chrono::durations without undefined
|
||||
* behaviour or erroneous results.
|
||||
* This is a stripped down version of duration_cast, for inclusion in fmt.
|
||||
* See https://github.com/pauldreik/safe_duration_cast
|
||||
*
|
||||
* Copyright Paul Dreik 2019
|
||||
*
|
||||
* This file is licensed under the fmt license, see format.h
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace safe_duration_cast {
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed ==
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
// A and B are both signed, or both unsigned.
|
||||
if (F::digits <= T::digits) {
|
||||
// From fits in To without any problem.
|
||||
} else {
|
||||
// From does not always fit in To, resort to a dynamic check.
|
||||
if (from < T::min() || from > T::max()) {
|
||||
// outside range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts From to To, without loss. If the dynamic value of from
|
||||
* can't be converted to To without loss, ec is set.
|
||||
*/
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
|
||||
std::numeric_limits<From>::is_signed !=
|
||||
std::numeric_limits<To>::is_signed)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using F = std::numeric_limits<From>;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(F::is_integer, "From must be integral");
|
||||
static_assert(T::is_integer, "To must be integral");
|
||||
|
||||
if (F::is_signed && !T::is_signed) {
|
||||
// From may be negative, not allowed!
|
||||
if (from < 0) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// From is positive. Can it always fit in 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())) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!F::is_signed && T::is_signed) {
|
||||
// can from be held in 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;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reaching here means all is ok for lossless conversion.
|
||||
return static_cast<To>(from);
|
||||
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
return from;
|
||||
} // function
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* converts From to To if possible, otherwise ec is set.
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------|---------------
|
||||
* NaN | NaN
|
||||
* Inf | Inf
|
||||
* normal, fits in output | converted (possibly lossy)
|
||||
* normal, does not fit in output | ec is set
|
||||
* subnormal | best effort
|
||||
* -Inf | -Inf
|
||||
*/
|
||||
// clang-format on
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(!std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
using T = std::numeric_limits<To>;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
static_assert(std::is_floating_point<To>::value, "To must be floating");
|
||||
|
||||
// catch the only happy case
|
||||
if (std::isfinite(from)) {
|
||||
if (from >= T::lowest() && from <= T::max()) {
|
||||
return static_cast<To>(from);
|
||||
}
|
||||
// not within range.
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
|
||||
// nan and inf will be preserved
|
||||
return static_cast<To>(from);
|
||||
} // function
|
||||
|
||||
template <typename To, typename From,
|
||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||
FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
||||
ec = 0;
|
||||
static_assert(std::is_floating_point<From>::value, "From must be floating");
|
||||
return from;
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration cast between integral durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_integral<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
using Factor = std::ratio_divide<typename From::period, typename To::period>;
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// safe conversion to IntermediateRep
|
||||
IntermediateRep count =
|
||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 =
|
||||
std::numeric_limits<IntermediateRep>::max() / Factor::num;
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 =
|
||||
std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= Factor::num;
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
count /= Factor::den;
|
||||
}
|
||||
// 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};
|
||||
}
|
||||
|
||||
/**
|
||||
* safe duration_cast between floating point durations
|
||||
*/
|
||||
template <typename To, typename FromRep, typename FromPeriod,
|
||||
FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
|
||||
FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
|
||||
To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
||||
int& ec) {
|
||||
using From = std::chrono::duration<FromRep, FromPeriod>;
|
||||
ec = 0;
|
||||
if (std::isnan(from.count())) {
|
||||
// nan in, gives nan out. easy.
|
||||
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
|
||||
}
|
||||
// maybe we should also check if from is denormal, and decide what to do about
|
||||
// it.
|
||||
|
||||
// +-inf should be preserved.
|
||||
if (std::isinf(from.count())) {
|
||||
return To{from.count()};
|
||||
}
|
||||
|
||||
// the basic idea is that we need to convert from count() in the from type
|
||||
// to count() in the To type, by multiplying it with this:
|
||||
using Factor = std::ratio_divide<typename From::period, typename To::period>;
|
||||
|
||||
static_assert(Factor::num > 0, "num must be positive");
|
||||
static_assert(Factor::den > 0, "den must be positive");
|
||||
|
||||
// the conversion is like this: multiply from.count() with Factor::num
|
||||
// /Factor::den and convert it to To::rep, all this without
|
||||
// overflow/underflow. let's start by finding a suitable type that can hold
|
||||
// both To, From and Factor::num
|
||||
using IntermediateRep =
|
||||
typename std::common_type<typename From::rep, typename To::rep,
|
||||
decltype(Factor::num)>::type;
|
||||
|
||||
// force conversion of From::rep -> IntermediateRep to be safe,
|
||||
// even if it will never happen be narrowing in this context.
|
||||
IntermediateRep count =
|
||||
safe_float_conversion<IntermediateRep>(from.count(), ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// multiply with Factor::num without overflow or underflow
|
||||
if (Factor::num != 1) {
|
||||
constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count > max1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
|
||||
static_cast<IntermediateRep>(Factor::num);
|
||||
if (count < min1) {
|
||||
ec = 1;
|
||||
return {};
|
||||
}
|
||||
count *= static_cast<IntermediateRep>(Factor::num);
|
||||
}
|
||||
|
||||
// this can't go wrong, right? den>0 is checked earlier.
|
||||
if (Factor::den != 1) {
|
||||
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
|
||||
count /= static_cast<common_t>(Factor::den);
|
||||
}
|
||||
|
||||
// convert to the to type, safely
|
||||
using ToRep = typename To::rep;
|
||||
|
||||
const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
|
||||
if (ec) {
|
||||
return {};
|
||||
}
|
||||
return To{tocount};
|
||||
}
|
||||
|
||||
} // namespace safe_duration_cast
|
||||
|
||||
FMT_END_NAMESPACE
|
143
src/format.cc
143
src/format.cc
@ -8,11 +8,125 @@
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template struct FMT_API internal::basic_data<void>;
|
||||
namespace internal {
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of grisu_format.
|
||||
bool (*instantiate_grisu_format)(double, internal::buffer<char>&, int, unsigned,
|
||||
int&) = internal::grisu_format;
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about nonliteral format string.
|
||||
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
struct sprintf_specs {
|
||||
int precision;
|
||||
char type;
|
||||
bool alt : 1;
|
||||
|
||||
template <typename Char>
|
||||
constexpr sprintf_specs(basic_format_specs<Char> specs)
|
||||
: precision(specs.precision), type(specs.type), alt(specs.alt) {}
|
||||
|
||||
constexpr bool has_precision() const { return precision >= 0; }
|
||||
};
|
||||
|
||||
// This is deprecated and is kept only to preserve ABI compatibility.
|
||||
template <typename Double>
|
||||
char* sprintf_format(Double value, internal::buffer<char>& buf,
|
||||
sprintf_specs specs) {
|
||||
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
||||
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
|
||||
|
||||
// Build format string.
|
||||
enum { max_format_size = 10 }; // longest format: %#-*.*Lg
|
||||
char format[max_format_size];
|
||||
char* format_ptr = format;
|
||||
*format_ptr++ = '%';
|
||||
if (specs.alt || !specs.type) *format_ptr++ = '#';
|
||||
if (specs.precision >= 0) {
|
||||
*format_ptr++ = '.';
|
||||
*format_ptr++ = '*';
|
||||
}
|
||||
if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
|
||||
|
||||
char type = specs.type;
|
||||
|
||||
if (type == '%')
|
||||
type = 'f';
|
||||
else if (type == 0 || type == 'n')
|
||||
type = 'g';
|
||||
#if FMT_MSC_VER
|
||||
if (type == 'F') {
|
||||
// MSVC's printf doesn't support 'F'.
|
||||
type = 'f';
|
||||
}
|
||||
#endif
|
||||
*format_ptr++ = type;
|
||||
*format_ptr = '\0';
|
||||
|
||||
// Format using snprintf.
|
||||
char* start = nullptr;
|
||||
char* decimal_point_pos = nullptr;
|
||||
for (;;) {
|
||||
std::size_t buffer_size = buf.capacity();
|
||||
start = &buf[0];
|
||||
int result =
|
||||
format_float(start, buffer_size, format, specs.precision, value);
|
||||
if (result >= 0) {
|
||||
unsigned n = internal::to_unsigned(result);
|
||||
if (n < buf.capacity()) {
|
||||
// Find the decimal point.
|
||||
auto p = buf.data(), end = p + n;
|
||||
if (*p == '+' || *p == '-') ++p;
|
||||
if (specs.type != 'a' && specs.type != 'A') {
|
||||
while (p < end && *p >= '0' && *p <= '9') ++p;
|
||||
if (p < end && *p != 'e' && *p != 'E') {
|
||||
decimal_point_pos = p;
|
||||
if (!specs.type) {
|
||||
// Keep only one trailing zero after the decimal point.
|
||||
++p;
|
||||
if (*p == '0') ++p;
|
||||
while (p != end && *p >= '1' && *p <= '9') ++p;
|
||||
char* where = p;
|
||||
while (p != end && *p == '0') ++p;
|
||||
if (p == end || *p < '0' || *p > '9') {
|
||||
if (p != end) std::memmove(where, p, to_unsigned(end - p));
|
||||
n -= static_cast<unsigned>(p - where);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.resize(n);
|
||||
break; // The buffer is large enough - continue with formatting.
|
||||
}
|
||||
buf.reserve(n + 1);
|
||||
} else {
|
||||
// If result is negative we ask to increase the capacity by at least 1,
|
||||
// but as std::vector, the buffer grows exponentially.
|
||||
buf.reserve(buf.capacity() + 1);
|
||||
}
|
||||
}
|
||||
return decimal_point_pos;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
template FMT_API char* internal::sprintf_format(long double,
|
||||
internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
|
||||
template struct FMT_INSTANTIATION_DEF_API internal::basic_data<void>;
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, internal::float_specs,
|
||||
internal::buffer<char>&) =
|
||||
internal::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
|
||||
@ -21,6 +135,7 @@ template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API char internal::decimal_point_impl(locale_ref);
|
||||
|
||||
@ -35,23 +150,27 @@ template FMT_API std::string internal::vformat<char>(
|
||||
template FMT_API format_context::iterator internal::vformat_to(
|
||||
internal::buffer<char>&, string_view, basic_format_args<format_context>);
|
||||
|
||||
template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
template FMT_API char* internal::sprintf_format(long double,
|
||||
internal::buffer<char>&,
|
||||
sprintf_specs);
|
||||
template FMT_API int internal::snprintf_float(double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::snprintf_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(double, int, internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
template FMT_API int internal::format_float(long double, int,
|
||||
internal::float_specs,
|
||||
internal::buffer<char>&);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
|
||||
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template FMT_API void internal::arg_map<wformat_context>::init(
|
||||
const basic_format_args<wformat_context>&);
|
||||
|
||||
template FMT_API std::wstring internal::vformat<wchar_t>(
|
||||
wstring_view, basic_format_args<wformat_context>);
|
||||
FMT_END_NAMESPACE
|
||||
|
@ -1,4 +1,4 @@
|
||||
// A C++ interface to POSIX functions.
|
||||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@ -10,37 +10,43 @@
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/posix.h"
|
||||
#include "fmt/os.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <climits>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
#else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
#if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
# include <windows.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
|
||||
#endif // _WIN32
|
||||
#endif
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
@ -49,7 +55,7 @@
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
typedef int RWResult;
|
||||
using RWResult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
@ -58,7 +64,7 @@ inline unsigned convert_rwcount(std::size_t count) {
|
||||
}
|
||||
#else
|
||||
// Return type of read and write functions.
|
||||
typedef ssize_t RWResult;
|
||||
using RWResult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
@ -66,6 +72,81 @@ inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int internal::utf16_to_utf8::convert(wstring_view s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
||||
length, nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void windows_error::init(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
error_code_ = err_code;
|
||||
memory_buffer buffer;
|
||||
internal::format_windows_error(buffer, err_code, vformat(format_str, args));
|
||||
std::runtime_error& base = *this;
|
||||
base = std::runtime_error(to_string(buffer));
|
||||
}
|
||||
|
||||
void internal::format_windows_error(internal::buffer<char>& out, int error_code,
|
||||
string_view message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
wmemory_buffer buf;
|
||||
buf.resize(inline_buffer_size);
|
||||
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;
|
||||
if (utf8_message.convert(system_message) == ERROR_SUCCESS) {
|
||||
internal::writer w(out);
|
||||
w.write(message);
|
||||
w.write(": ");
|
||||
w.write(utf8_message);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
break; // Can't get error message, report error code instead.
|
||||
buf.resize(buf.size() * 2);
|
||||
}
|
||||
}
|
||||
FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
void report_windows_error(int error_code,
|
||||
fmt::string_view message) FMT_NOEXCEPT {
|
||||
report_error(internal::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
@ -94,14 +175,15 @@ int buffered_file::fileno() const {
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
#else
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
#endif
|
||||
# endif
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
}
|
||||
@ -123,7 +205,7 @@ void file::close() {
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
@ -137,15 +219,15 @@ long long file::size() const {
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
#else
|
||||
typedef struct stat Stat;
|
||||
# else
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
@ -192,15 +274,15 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
int fds[2] = {};
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
#else
|
||||
# else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
#endif
|
||||
# endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
@ -220,14 +302,15 @@ buffered_file file::fdopen(const char* mode) {
|
||||
}
|
||||
|
||||
long getpagesize() {
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
#else
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
2
support/Vagrantfile
vendored
2
support/Vagrantfile
vendored
@ -3,7 +3,7 @@
|
||||
|
||||
# A vagrant config for testing against gcc-4.8.
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
vb.memory = "4096"
|
||||
|
@ -48,17 +48,6 @@ endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
||||
|
||||
# Check if variadic templates are working and not affected by GCC bug 39653:
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=39653
|
||||
# Can be removed once gcc 4.4 support is dropped.
|
||||
check_cxx_source_compiles("
|
||||
template <class T, class ...Types>
|
||||
struct S { typedef typename S<Types...>::type type; };
|
||||
int main() {}" SUPPORTS_VARIADIC_TEMPLATES)
|
||||
if (NOT SUPPORTS_VARIADIC_TEMPLATES)
|
||||
set (SUPPORTS_VARIADIC_TEMPLATES OFF)
|
||||
endif ()
|
||||
|
||||
# Check if user-defined literals are available
|
||||
check_cxx_source_compiles("
|
||||
void operator\"\" _udl(long double);
|
||||
|
@ -1,11 +1,11 @@
|
||||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=@CMAKE_INSTALL_FULL_LIBDIR@
|
||||
includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
|
||||
libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
|
||||
includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
|
||||
|
||||
Name: fmt
|
||||
Description: A modern formatting library
|
||||
Version: @FMT_VERSION@
|
||||
Libs: -L${libdir} -lfmt
|
||||
Libs: -L${libdir} -l@FMT_LIB_NAME@
|
||||
Cflags: -I${includedir}
|
||||
|
||||
|
@ -137,7 +137,7 @@ def update_site(env):
|
||||
if not os.path.exists(contents):
|
||||
os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
|
||||
# Fix issues in reference.rst/api.rst.
|
||||
for filename in ['reference.rst', 'api.rst']:
|
||||
for filename in ['reference.rst', 'api.rst', 'index.rst']:
|
||||
pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
|
||||
with rewrite(os.path.join(target_doc_dir, filename)) as b:
|
||||
b.data = b.data.replace('std::ostream &', 'std::ostream&')
|
||||
@ -146,6 +146,7 @@ def update_site(env):
|
||||
b.data = b.data.replace('unsigned int', 'unsigned')
|
||||
b.data = b.data.replace('operator""_', 'operator"" _')
|
||||
b.data = b.data.replace(', size_t', ', std::size_t')
|
||||
b.data = b.data.replace('aa long', 'a long')
|
||||
# Fix a broken link in index.rst.
|
||||
index = os.path.join(target_doc_dir, 'index.rst')
|
||||
with rewrite(index) as b:
|
||||
|
@ -17,9 +17,7 @@ else ()
|
||||
target_compile_definitions(gmock PUBLIC GTEST_HAS_PTHREAD=0)
|
||||
endif ()
|
||||
|
||||
if (NOT SUPPORTS_VARIADIC_TEMPLATES)
|
||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
||||
endif ()
|
||||
target_compile_definitions(gmock PUBLIC GTEST_LANG_CXX11=0)
|
||||
|
||||
if (MSVC)
|
||||
# Workaround a bug in implementation of variadic templates in MSVC11.
|
||||
@ -47,8 +45,6 @@ target_compile_definitions(gmock
|
||||
|
||||
set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
|
||||
add_library(test-main STATIC ${TEST_MAIN_SRC})
|
||||
target_compile_definitions(test-main PUBLIC
|
||||
FMT_USE_FILE_DESCRIPTORS=$<BOOL:${HAVE_OPEN}>)
|
||||
target_include_directories(test-main SYSTEM PUBLIC gtest gmock)
|
||||
target_link_libraries(test-main gmock fmt)
|
||||
|
||||
@ -84,6 +80,9 @@ function(add_fmt_test name)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(${name} PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
|
||||
add_test(NAME ${name} COMMAND ${name})
|
||||
endfunction()
|
||||
@ -96,6 +95,9 @@ add_fmt_test(grisu-test)
|
||||
target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
|
||||
add_fmt_test(gtest-extra-test)
|
||||
add_fmt_test(format-test mock-allocator.h)
|
||||
if (MSVC)
|
||||
target_compile_options(format-test PRIVATE /bigobj)
|
||||
endif ()
|
||||
if (NOT (MSVC AND BUILD_SHARED_LIBS))
|
||||
add_fmt_test(format-impl-test)
|
||||
endif ()
|
||||
@ -107,12 +109,11 @@ add_fmt_test(custom-formatter-test)
|
||||
add_fmt_test(ranges-test)
|
||||
add_fmt_test(scan-test)
|
||||
|
||||
if (HAVE_OPEN)
|
||||
if (NOT MSVC_BUILD_STATIC)
|
||||
add_fmt_executable(posix-mock-test
|
||||
posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
|
||||
target_include_directories(
|
||||
posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(posix-mock-test PRIVATE FMT_USE_FILE_DESCRIPTORS=1)
|
||||
target_link_libraries(posix-mock-test gmock)
|
||||
target_include_directories(posix-mock-test SYSTEM PUBLIC gtest gmock)
|
||||
if (FMT_PEDANTIC)
|
||||
@ -122,7 +123,7 @@ if (HAVE_OPEN)
|
||||
target_compile_definitions(posix-mock-test PRIVATE FMT_LOCALE)
|
||||
endif ()
|
||||
add_test(NAME posix-mock-test COMMAND posix-mock-test)
|
||||
add_fmt_test(posix-test)
|
||||
add_fmt_test(os-test)
|
||||
endif ()
|
||||
|
||||
add_fmt_executable(header-only-test
|
||||
@ -168,18 +169,6 @@ if (FMT_PEDANTIC)
|
||||
target_compile_definitions(
|
||||
nolocale-test PRIVATE FMT_STATIC_THOUSANDS_SEPARATOR=1)
|
||||
|
||||
# Test that the library compiles without windows.h.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
add_library(no-windows-h-test ../src/format.cc)
|
||||
target_include_directories(
|
||||
no-windows-h-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(no-windows-h-test PRIVATE FMT_USE_WINDOWS_H=0)
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(no-windows-h-test PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
target_include_directories(no-windows-h-test SYSTEM PUBLIC gtest gmock)
|
||||
endif ()
|
||||
|
||||
add_test(compile-error-test ${CMAKE_CTEST_COMMAND}
|
||||
--build-and-test
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/compile-error-test"
|
||||
@ -225,3 +214,23 @@ if (FMT_PEDANTIC AND NOT WIN32)
|
||||
"-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
|
||||
endif ()
|
||||
|
||||
# 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
|
||||
if (FMT_CUDA_TEST)
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
find_package(CUDA 9.0)
|
||||
else ()
|
||||
include(CheckLanguage)
|
||||
check_language(CUDA)
|
||||
if (CMAKE_CUDA_COMPILER)
|
||||
enable_language(CUDA OPTIONAL)
|
||||
set(CUDA_FOUND TRUE)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CUDA_FOUND)
|
||||
add_subdirectory(cuda-test)
|
||||
add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
|
||||
endif ()
|
||||
endif ()
|
||||
|
@ -8,15 +8,21 @@
|
||||
#include "fmt/core.h"
|
||||
#include "gtest.h"
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
EXPECT_DEBUG_DEATH(statement, regex)
|
||||
#else
|
||||
# define EXPECT_DEBUG_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, )
|
||||
#endif
|
||||
|
||||
TEST(AssertTest, Fail) {
|
||||
EXPECT_DEBUG_DEATH_IF_SUPPORTED(FMT_ASSERT(false, "don't panic!"),
|
||||
"don't panic!");
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
EXPECT_DEBUG_DEATH(FMT_ASSERT(false, "don't panic!"), "don't panic!");
|
||||
#else
|
||||
fmt::print("warning: death tests are not supported\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool test_condition = false;
|
||||
|
||||
TEST(AssertTest, DanglingElse) {
|
||||
bool executed_else = false;
|
||||
if (test_condition)
|
||||
FMT_ASSERT(true, "");
|
||||
else
|
||||
executed_else = true;
|
||||
EXPECT_TRUE(executed_else);
|
||||
}
|
||||
|
@ -145,6 +145,48 @@ TEST(ChronoTest, FormatDefault) {
|
||||
fmt::format("{}", std::chrono::duration<int, std::ratio<15, 4>>(42)));
|
||||
}
|
||||
|
||||
TEST(ChronoTest, FormatWide) {
|
||||
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);
|
||||
EXPECT_EQ("42s ", fmt::format("{:5}", s));
|
||||
|
@ -47,9 +47,11 @@ TEST(ColorsTest, ColorsPrint) {
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
}
|
||||
|
||||
TEST(ColorsTest, ColorsFormat) {
|
||||
TEST(ColorsTest, Format) {
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
|
||||
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 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"),
|
||||
"\x1b[38;2;000;000;255mblue\x1b[0m");
|
||||
EXPECT_EQ(
|
||||
@ -78,4 +80,6 @@ TEST(ColorsTest, ColorsFormat) {
|
||||
"\x1b[92mtbgreen\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
|
||||
"\x1b[105mtbmagenta\x1b[0m");
|
||||
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
|
||||
"\x1b[31mfoo\x1b[0m");
|
||||
}
|
||||
|
@ -34,205 +34,17 @@
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace internal {
|
||||
bool operator==(const internal::string_view_metadata& lhs,
|
||||
const internal::string_view_metadata& rhs) {
|
||||
return std::tie(lhs.offset_, lhs.size_) == std::tie(rhs.offset_, rhs.size_);
|
||||
}
|
||||
bool operator!=(const internal::string_view_metadata& lhs,
|
||||
const internal::string_view_metadata& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const format_part<char>::specification& lhs,
|
||||
const format_part<char>::specification& rhs) {
|
||||
if (lhs.arg_id.which != rhs.arg_id.which) {
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef format_part<char>::argument_id::which_arg_id which_arg_id;
|
||||
switch (lhs.arg_id.which) {
|
||||
case which_arg_id::index: {
|
||||
if (lhs.arg_id.val.index != rhs.arg_id.val.index) {
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case which_arg_id::named_index: {
|
||||
if (lhs.arg_id.val.named_index != rhs.arg_id.val.named_index) {
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
|
||||
return std::tie(lhs.parsed_specs.width, lhs.parsed_specs.fill[0],
|
||||
lhs.parsed_specs.align, lhs.parsed_specs.precision,
|
||||
lhs.parsed_specs.sign, lhs.parsed_specs.type) ==
|
||||
std::tie(rhs.parsed_specs.width, rhs.parsed_specs.fill[0],
|
||||
rhs.parsed_specs.align, rhs.parsed_specs.precision,
|
||||
rhs.parsed_specs.sign, rhs.parsed_specs.type);
|
||||
}
|
||||
|
||||
bool operator!=(const format_part<char>::specification& lhs,
|
||||
const format_part<char>::specification& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
bool operator==(const format_part<char>& lhs,
|
||||
const fmt::internal::format_part<char>& rhs) {
|
||||
typedef format_part<char>::kind kind;
|
||||
|
||||
if (lhs.which != rhs.which ||
|
||||
lhs.end_of_argument_id != rhs.end_of_argument_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (lhs.which) {
|
||||
case kind::argument_id: {
|
||||
return lhs.val.arg_id == rhs.val.arg_id;
|
||||
}
|
||||
|
||||
case kind::named_argument_id: {
|
||||
return lhs.val.named_arg_id == rhs.val.named_arg_id;
|
||||
}
|
||||
|
||||
case kind::text: {
|
||||
return lhs.val.text == rhs.val.text;
|
||||
}
|
||||
|
||||
case kind::specification: {
|
||||
return lhs.val.spec == rhs.val.spec;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator!=(const fmt::internal::format_part<char>& lhs,
|
||||
const fmt::internal::format_part<char>& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatPart_ComparisonOperators) {
|
||||
typedef fmt::internal::format_part<char> format_part;
|
||||
typedef fmt::internal::dynamic_format_specs<char> prepared_specs;
|
||||
|
||||
{
|
||||
const auto part = format_part(0u);
|
||||
const auto other = format_part(0u);
|
||||
EXPECT_EQ(part, other);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(0u);
|
||||
const auto rhs = format_part(1u);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
const auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
const auto rhs = format_part(fmt::internal::string_view_metadata(0, 4422));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
auto lhs = format_part(0u);
|
||||
auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
rhs = format_part(fmt::internal::string_view_metadata(0, 0));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
auto lhs = format_part(0u);
|
||||
lhs.end_of_argument_id = 42;
|
||||
auto rhs = format_part(0u);
|
||||
rhs.end_of_argument_id = 42;
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
rhs.end_of_argument_id = 13;
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto specs_argument_id = 0u;
|
||||
const auto specs_named_argument_id =
|
||||
fmt::internal::string_view_metadata(0, 42);
|
||||
auto specs = format_part::specification(0u);
|
||||
auto lhs = format_part(specs);
|
||||
auto rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs = format_part::specification(specs_named_argument_id);
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
rhs = format_part(specs);
|
||||
EXPECT_EQ(lhs, rhs);
|
||||
|
||||
auto lhs_spec = format_part::specification(specs_argument_id);
|
||||
auto rhs_spec = format_part::specification(specs_named_argument_id);
|
||||
lhs = format_part(lhs_spec);
|
||||
rhs = format_part(rhs_spec);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
|
||||
lhs_spec = format_part::specification(specs_argument_id);
|
||||
rhs_spec = format_part::specification(specs_argument_id);
|
||||
lhs_spec.parsed_specs.precision = 1;
|
||||
rhs_spec.parsed_specs.precision = 2;
|
||||
lhs = format_part(lhs_spec);
|
||||
rhs = format_part(rhs_spec);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
}
|
||||
{
|
||||
const auto specs_argument_id = 0u;
|
||||
const auto specs_named_argument_id =
|
||||
fmt::internal::string_view_metadata(0, 42);
|
||||
auto specs = format_part::specification(specs_argument_id);
|
||||
auto lhs = format_part(specs);
|
||||
auto rhs = format_part(0u);
|
||||
auto rhs2 = format_part(fmt::internal::string_view_metadata(0, 42));
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part{specs};
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs = format_part::specification(specs_named_argument_id);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
|
||||
specs.parsed_specs = prepared_specs();
|
||||
lhs = format_part(specs);
|
||||
EXPECT_NE(lhs, rhs);
|
||||
EXPECT_NE(lhs, rhs2);
|
||||
}
|
||||
}
|
||||
|
||||
// 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::internal::compiletime_prepared_parts_type_provider<decltype(
|
||||
format)>
|
||||
provider;
|
||||
typedef typename provider::template format_parts_array<EXPECTED_PARTS_COUNT>
|
||||
expected_parts_type;
|
||||
static_assert(
|
||||
std::is_same<typename provider::type, expected_parts_type>::value,
|
||||
"CompileTimePreparedPartsTypeProvider test failed");
|
||||
typedef fmt::internal::compiled_format_base<decltype(format)> provider;
|
||||
typedef fmt::internal::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) {
|
||||
@ -253,40 +65,6 @@ TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class custom_parts_container {
|
||||
public:
|
||||
typedef fmt::internal::format_part<char> format_part_type;
|
||||
|
||||
private:
|
||||
typedef std::deque<format_part_type> parts;
|
||||
|
||||
public:
|
||||
void add(format_part_type part) { parts_.push_back(std::move(part)); }
|
||||
|
||||
void substitute_last(format_part_type part) {
|
||||
parts_.back() = std::move(part);
|
||||
}
|
||||
|
||||
format_part_type last() { return parts_.back(); }
|
||||
|
||||
auto begin() -> decltype(std::declval<parts>().begin()) {
|
||||
return parts_.begin();
|
||||
}
|
||||
|
||||
auto begin() const -> decltype(std::declval<const parts>().begin()) {
|
||||
return parts_.begin();
|
||||
}
|
||||
|
||||
auto end() -> decltype(std::declval<parts>().begin()) { return parts_.end(); }
|
||||
|
||||
auto end() const -> decltype(std::declval<const parts>().begin()) {
|
||||
return parts_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
parts parts_;
|
||||
};
|
||||
|
||||
TEST(CompileTest, PassStringLiteralFormat) {
|
||||
const auto prepared = fmt::compile<int>("test {}");
|
||||
EXPECT_EQ("test 42", fmt::format(prepared, 42));
|
||||
@ -338,3 +116,28 @@ TEST(CompileTest, FormattedSize) {
|
||||
auto f = fmt::compile<int>("{:10}");
|
||||
EXPECT_EQ(fmt::formatted_size(f, 42), 10);
|
||||
}
|
||||
|
||||
TEST(CompileTest, MultipleTypes) {
|
||||
auto f = fmt::compile<int, int>("{} {}");
|
||||
EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
|
||||
}
|
||||
|
||||
struct formattable {};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<formattable> : formatter<const char*> {
|
||||
auto format(formattable, format_context& ctx) -> decltype(ctx.out()) {
|
||||
return formatter<const char*>::format("foo", ctx);
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CompileTest, FormatUserDefinedType) {
|
||||
auto f = fmt::compile<formattable>("{}");
|
||||
EXPECT_EQ(fmt::format(f, formattable()), "foo");
|
||||
}
|
||||
|
||||
TEST(CompileTest, EmptyFormatString) {
|
||||
auto f = fmt::compile<>("");
|
||||
EXPECT_EQ(fmt::format(f), "");
|
||||
}
|
||||
|
@ -15,9 +15,8 @@
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "test-assert.h"
|
||||
|
||||
#include "gmock.h"
|
||||
#include "test-assert.h"
|
||||
|
||||
// Check if fmt/core.h compiles with windows.h included before it.
|
||||
#ifdef _WIN32
|
||||
@ -103,7 +102,7 @@ template <typename T> struct mock_buffer : buffer<T> {
|
||||
TEST(BufferTest, Ctor) {
|
||||
{
|
||||
mock_buffer<int> buffer;
|
||||
EXPECT_EQ(nullptr, &buffer[0]);
|
||||
EXPECT_EQ(nullptr, buffer.data());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
|
||||
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
|
||||
}
|
||||
@ -127,8 +126,13 @@ TEST(BufferTest, Ctor) {
|
||||
struct dying_buffer : test_buffer<int> {
|
||||
MOCK_METHOD0(die, void());
|
||||
~dying_buffer() { die(); }
|
||||
|
||||
private:
|
||||
virtual void avoid_weak_vtable();
|
||||
};
|
||||
|
||||
void dying_buffer::avoid_weak_vtable() {}
|
||||
|
||||
TEST(BufferTest, VirtualDtor) {
|
||||
typedef StrictMock<dying_buffer> stict_mock_buffer;
|
||||
stict_mock_buffer* mock_buffer = new stict_mock_buffer();
|
||||
@ -285,8 +289,6 @@ VISIT_TYPE(long, long long);
|
||||
VISIT_TYPE(unsigned long, unsigned long long);
|
||||
#endif
|
||||
|
||||
VISIT_TYPE(float, double);
|
||||
|
||||
#define CHECK_ARG_(Char, expected, value) \
|
||||
{ \
|
||||
testing::StrictMock<mock_visitor<decltype(expected)>> visitor; \
|
||||
@ -399,8 +401,114 @@ TEST(ArgTest, VisitInvalidArg) {
|
||||
fmt::visit_format_arg(visitor, arg);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, Basic) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc1");
|
||||
store.push_back(1.5f);
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, StringsAndRefs) {
|
||||
// Unfortunately the tests are compiled with old ABI so strings use COW.
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char str[] = "1234567890";
|
||||
store.push_back(str);
|
||||
store.push_back(std::cref(str));
|
||||
store.push_back(fmt::string_view{str});
|
||||
str[0] = 'X';
|
||||
|
||||
std::string 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(FormatDynArgsTest, CustomFormat) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
custom_type c{};
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(c);
|
||||
++c.i;
|
||||
store.push_back(std::cref(c));
|
||||
++c.i;
|
||||
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
EXPECT_EQ("cust=0 and cust=1 and cust=3", result);
|
||||
}
|
||||
|
||||
TEST(FormatDynArgsTest, NamedArgByRef) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
|
||||
// Note: fmt::arg() constructs an object which holds a reference
|
||||
// to its value. It's not an aggregate, so it doesn't extend the
|
||||
// reference lifetime. As a result, it's a very bad idea passing temporary
|
||||
// as a named argument value. Only GCC with optimization level >0
|
||||
// complains about this.
|
||||
//
|
||||
// A real life usecase is when you have both name and value alive
|
||||
// guarantee their lifetime and thus don't want them to be copied into
|
||||
// storages.
|
||||
int a1_val{42};
|
||||
auto a1 = fmt::arg("a1_", a1_val);
|
||||
store.push_back(std::cref(a1));
|
||||
|
||||
std::string result = fmt::vformat("{a1_}", // and {} and {}",
|
||||
store);
|
||||
|
||||
EXPECT_EQ("42", 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(FormatDynArgsTest, ThrowOnCopy) {
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(std::string("foo"));
|
||||
try {
|
||||
store.push_back(copy_throwable());
|
||||
} catch (...) {
|
||||
}
|
||||
EXPECT_EQ(fmt::vformat("{}", store), "foo");
|
||||
}
|
||||
|
||||
TEST(StringViewTest, ValueType) {
|
||||
static_assert(std::is_same<string_view::value_type, char>::value, "");
|
||||
}
|
||||
|
||||
TEST(StringViewTest, Length) {
|
||||
// Test that StringRef::size() returns string length, not buffer size.
|
||||
// Test that string_view::size() returns string length, not buffer size.
|
||||
char str[100] = "some string";
|
||||
EXPECT_EQ(std::strlen(str), string_view(str).size());
|
||||
EXPECT_LT(std::strlen(str), sizeof(str));
|
||||
@ -450,17 +558,22 @@ template <> struct formatter<enabled_formatter> {
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CoreTest, HasFormatter) {
|
||||
using fmt::internal::has_formatter;
|
||||
using fmt::has_formatter;
|
||||
using context = fmt::format_context;
|
||||
EXPECT_TRUE((has_formatter<enabled_formatter, context>::value));
|
||||
EXPECT_FALSE((has_formatter<disabled_formatter, context>::value));
|
||||
EXPECT_FALSE((has_formatter<disabled_formatter_convertible, context>::value));
|
||||
static_assert(has_formatter<enabled_formatter, context>::value, "");
|
||||
static_assert(!has_formatter<disabled_formatter, context>::value, "");
|
||||
static_assert(!has_formatter<disabled_formatter_convertible, context>::value,
|
||||
"");
|
||||
}
|
||||
|
||||
struct convertible_to_int {
|
||||
operator int() const { return 42; }
|
||||
};
|
||||
|
||||
struct convertible_to_c_string {
|
||||
operator const char*() const { return "foo"; }
|
||||
};
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
template <> struct formatter<convertible_to_int> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
@ -470,10 +583,21 @@ template <> struct formatter<convertible_to_int> {
|
||||
return std::copy_n("foo", 3, ctx.out());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct formatter<convertible_to_c_string> {
|
||||
auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
auto format(convertible_to_c_string, format_context& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
return std::copy_n("bar", 3, ctx.out());
|
||||
}
|
||||
};
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
TEST(CoreTest, FormatterOverridesImplicitConversion) {
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_int()), "foo");
|
||||
EXPECT_EQ(fmt::format("{}", convertible_to_c_string()), "bar");
|
||||
}
|
||||
|
||||
namespace my_ns {
|
||||
@ -569,13 +693,13 @@ TEST(CoreTest, ToStringViewForeignStrings) {
|
||||
fmt::internal::type type =
|
||||
fmt::internal::mapped_type_constant<my_string<char>,
|
||||
fmt::format_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::string_type);
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
type = fmt::internal::mapped_type_constant<my_string<wchar_t>,
|
||||
fmt::wformat_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::string_type);
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
type =
|
||||
fmt::internal::mapped_type_constant<QString, fmt::wformat_context>::value;
|
||||
EXPECT_EQ(type, fmt::internal::string_type);
|
||||
EXPECT_EQ(type, fmt::internal::type::string_type);
|
||||
// Does not compile: only wide format contexts are compatible with QString!
|
||||
// type = fmt::internal::mapped_type_constant<QString,
|
||||
// fmt::format_context>::value;
|
||||
@ -613,6 +737,17 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToStringView) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view()));
|
||||
}
|
||||
|
||||
# ifdef FMT_USE_STRING_VIEW
|
||||
struct explicitly_convertible_to_std_string_view {
|
||||
explicit operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
EXPECT_EQ("foo",
|
||||
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
||||
}
|
||||
# endif
|
||||
|
||||
struct explicitly_convertible_to_wstring_view {
|
||||
explicit operator fmt::wstring_view() const { return L"foo"; }
|
||||
};
|
||||
@ -621,17 +756,15 @@ TEST(FormatterTest, FormatExplicitlyConvertibleToWStringView) {
|
||||
EXPECT_EQ(L"foo",
|
||||
fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
|
||||
}
|
||||
#endif
|
||||
|
||||
struct explicitly_convertible_to_string_like {
|
||||
template <typename String,
|
||||
typename = typename std::enable_if<std::is_constructible<
|
||||
String, const char*, std::size_t>::value>::type>
|
||||
explicit operator String() const {
|
||||
return String("foo", 3u);
|
||||
}
|
||||
struct disabled_rvalue_conversion {
|
||||
operator const char*() const& { return "foo"; }
|
||||
operator const char*() & { return "foo"; }
|
||||
operator const char*() const&& = delete;
|
||||
operator const char*() && = delete;
|
||||
};
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like()));
|
||||
TEST(FormatterTest, DisabledRValueConversion) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
|
||||
}
|
||||
#endif
|
||||
|
73
test/cuda-test/CMakeLists.txt
Normal file
73
test/cuda-test/CMakeLists.txt
Normal file
@ -0,0 +1,73 @@
|
||||
# We can find some usecases which follow the guide of CMake which uses
|
||||
# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake
|
||||
# built-in functions use NVCC.
|
||||
|
||||
# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement
|
||||
#
|
||||
# However, this requires CMake version 3.10 or higher and we can't be sure most
|
||||
# of the CUDA projects are using those.
|
||||
#
|
||||
# This test relies on `find_package(CUDA)` in the parent CMake config.
|
||||
|
||||
# These can be updated when NVCC becomes ready for C++ 17 features
|
||||
# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
|
||||
set(CMAKE_CUDA_STANDARD 14)
|
||||
set(CMAKE_CUDA_STANDARD_REQUIRED 14)
|
||||
|
||||
# In this test, we assume that the user is going to compile CUDA source code
|
||||
# with some libraries (fmt in this case).
|
||||
#
|
||||
# In addition to that, this test invokes both the C++ host compiler and NVCC
|
||||
# by providing another (non-CUDA) C++ source code.
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.15)
|
||||
# https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html
|
||||
list(APPEND CUDA_NVCC_FLAGS "-std=c++14")
|
||||
if (MSVC)
|
||||
# This is the solution of pytorch:
|
||||
# https://github.com/pytorch/pytorch/pull/7118
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14")
|
||||
list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus")
|
||||
# for the reason of this -Xcompiler options, see below.
|
||||
endif ()
|
||||
cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# This part is for (non-CUDA) C++ code. MSVC can define incorrect
|
||||
# `__cplusplus` macro. Fix for the issue is to use additional compiler flag.
|
||||
#
|
||||
# See Also:
|
||||
# https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
|
||||
# https://github.com/Microsoft/vscode-cpptools/issues/2595
|
||||
target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-)
|
||||
endif ()
|
||||
else()
|
||||
# now using a "new" way of handling CUDA
|
||||
add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
|
||||
set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
|
||||
if (MSVC)
|
||||
# with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will
|
||||
# not set __cplusplus correctly anyway, while nvcc will ignore it.
|
||||
# If specified for nvcc on the command line as '-std=c++14' nvcc will emit this
|
||||
# message instead:
|
||||
# nvcc warning : The -std=c++14 flag is not supported with the configured host
|
||||
# compiler. Flag will be ignored.
|
||||
set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY
|
||||
COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus)
|
||||
set_property(SOURCE cpp14.cc APPEND PROPERTY
|
||||
COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD)
|
||||
message(STATUS "cuda_standard: ${IN_USE_CUDA_STANDARD}")
|
||||
|
||||
get_target_property(IN_USE_CUDA_STANDARD_REQUIRED
|
||||
fmt-in-cuda-test CUDA_STANDARD_REQUIRED)
|
||||
message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}")
|
||||
|
||||
# We don't use PUBLIC or other keyword for reasons explained in the
|
||||
# CUDA_LINK_LIBRARIES_KEYWORD section in
|
||||
# https://cmake.org/cmake/help/latest/module/FindCUDA.html
|
||||
target_link_libraries(fmt-in-cuda-test fmt::fmt)
|
||||
|
11
test/cuda-test/cpp14.cc
Normal file
11
test/cuda-test/cpp14.cc
Normal file
@ -0,0 +1,11 @@
|
||||
#include <fmt/core.h>
|
||||
|
||||
// The purpose of this part is to ensure NVCC's host compiler also supports
|
||||
// the standard version. See 'cuda-cpp14.cu'.
|
||||
//
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler");
|
||||
|
||||
auto make_message_cpp() -> std::string {
|
||||
return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
28
test/cuda-test/cuda-cpp14.cu
Normal file
28
test/cuda-test/cuda-cpp14.cu
Normal file
@ -0,0 +1,28 @@
|
||||
// Direct NVCC command line example:
|
||||
//
|
||||
// nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \
|
||||
// -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus
|
||||
|
||||
// Ensure that we are using the latest C++ standard for NVCC
|
||||
// The version is C++14
|
||||
//
|
||||
// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support
|
||||
// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
|
||||
static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc");
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <iostream>
|
||||
|
||||
extern auto make_message_cpp() -> std::string;
|
||||
extern auto make_message_cuda() -> std::string;
|
||||
|
||||
int main() {
|
||||
std::cout << make_message_cuda() << std::endl;
|
||||
std::cout << make_message_cpp() << std::endl;
|
||||
}
|
||||
|
||||
auto make_message_cuda() -> std::string {
|
||||
return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus);
|
||||
}
|
@ -18,9 +18,9 @@
|
||||
// A custom argument formatter that doesn't print `-` for floating-point values
|
||||
// rounded to 0.
|
||||
class custom_arg_formatter
|
||||
: public fmt::arg_formatter<fmt::internal::buffer_range<char>> {
|
||||
: public fmt::arg_formatter<fmt::buffer_range<char>> {
|
||||
public:
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
typedef fmt::arg_formatter<range> base;
|
||||
|
||||
custom_arg_formatter(fmt::format_context& ctx,
|
||||
|
76
test/format
76
test/format
@ -8,6 +8,7 @@
|
||||
#ifndef FMT_FORMAT_
|
||||
#define FMT_FORMAT_
|
||||
|
||||
#include <cassert>
|
||||
#include <variant>
|
||||
#include "fmt/format.h"
|
||||
|
||||
@ -82,9 +83,9 @@ namespace std {
|
||||
Out format_to(Out out, wstring_view fmt, const Args&... args);
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<Out, char> args);
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args);
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<Out, wchar_t> args);
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
|
||||
|
||||
template<class Out>
|
||||
struct format_to_n_result {
|
||||
@ -533,31 +534,31 @@ inline fmt::internal::type get_type(basic_format_arg<Context> arg) {
|
||||
using char_type = typename Context::char_type;
|
||||
using T = decltype(val);
|
||||
if (std::is_same_v<T, monostate>)
|
||||
return fmt::internal::none_type;
|
||||
return fmt::internal::type::none_type;
|
||||
if (std::is_same_v<T, bool>)
|
||||
return fmt::internal::bool_type;
|
||||
return fmt::internal::type::bool_type;
|
||||
if (std::is_same_v<T, char_type>)
|
||||
return fmt::internal::char_type;
|
||||
return fmt::internal::type::char_type;
|
||||
if (std::is_same_v<T, int>)
|
||||
return fmt::internal::int_type;
|
||||
return fmt::internal::type::int_type;
|
||||
if (std::is_same_v<T, unsigned int>)
|
||||
return fmt::internal::uint_type;
|
||||
return fmt::internal::type::uint_type;
|
||||
if (std::is_same_v<T, long long int>)
|
||||
return fmt::internal::long_long_type;
|
||||
return fmt::internal::type::long_long_type;
|
||||
if (std::is_same_v<T, unsigned long long int>)
|
||||
return fmt::internal::ulong_long_type;
|
||||
return fmt::internal::type::ulong_long_type;
|
||||
if (std::is_same_v<T, double>)
|
||||
return fmt::internal::double_type;
|
||||
return fmt::internal::type::double_type;
|
||||
if (std::is_same_v<T, long double>)
|
||||
return fmt::internal::long_double_type;
|
||||
return fmt::internal::type::long_double_type;
|
||||
if (std::is_same_v<T, const char_type*>)
|
||||
return fmt::internal::cstring_type;
|
||||
return fmt::internal::type::cstring_type;
|
||||
if (std::is_same_v<T, basic_string_view<char_type>>)
|
||||
return fmt::internal::string_type;
|
||||
return fmt::internal::type::string_type;
|
||||
if (std::is_same_v<T, const void*>)
|
||||
return fmt::internal::pointer_type;
|
||||
return fmt::internal::type::pointer_type;
|
||||
assert(get_value(arg).index() == 12);
|
||||
return fmt::internal::custom_type;
|
||||
return fmt::internal::type::custom_type;
|
||||
}, arg);
|
||||
}
|
||||
|
||||
@ -648,38 +649,37 @@ struct formatter {
|
||||
auto type_spec = specs_.type;
|
||||
auto eh = ctx.error_handler();
|
||||
switch (type) {
|
||||
case internal::none_type:
|
||||
case internal::named_arg_type:
|
||||
case internal::type::none_type:
|
||||
case internal::type::named_arg_type:
|
||||
FMT_ASSERT(false, "invalid argument type");
|
||||
break;
|
||||
case internal::int_type:
|
||||
case internal::uint_type:
|
||||
case internal::long_long_type:
|
||||
case internal::ulong_long_type:
|
||||
case internal::bool_type:
|
||||
case internal::type::int_type:
|
||||
case internal::type::uint_type:
|
||||
case internal::type::long_long_type:
|
||||
case internal::type::ulong_long_type:
|
||||
case internal::type::bool_type:
|
||||
handle_int_type_spec(type_spec,
|
||||
internal::int_type_checker<decltype(eh)>(eh));
|
||||
break;
|
||||
case internal::char_type:
|
||||
case internal::type::char_type:
|
||||
handle_char_specs(
|
||||
&specs_, internal::char_specs_checker<decltype(eh)>(type_spec, eh));
|
||||
break;
|
||||
case internal::double_type:
|
||||
case internal::long_double_type:
|
||||
handle_float_type_spec(type_spec,
|
||||
internal::float_type_checker<decltype(eh)>(eh));
|
||||
case internal::type::double_type:
|
||||
case internal::type::long_double_type:
|
||||
internal::parse_float_type_spec(specs_, eh);
|
||||
break;
|
||||
case internal::cstring_type:
|
||||
case internal::type::cstring_type:
|
||||
internal::handle_cstring_type_spec(
|
||||
type_spec, internal::cstring_type_checker<decltype(eh)>(eh));
|
||||
break;
|
||||
case internal::string_type:
|
||||
case internal::type::string_type:
|
||||
internal::check_string_type_spec(type_spec, eh);
|
||||
break;
|
||||
case internal::pointer_type:
|
||||
case internal::type::pointer_type:
|
||||
internal::check_pointer_type_spec(type_spec, eh);
|
||||
break;
|
||||
case internal::custom_type:
|
||||
case internal::type::custom_type:
|
||||
// Custom format specifiers should be checked in parse functions of
|
||||
// formatter specializations.
|
||||
break;
|
||||
@ -690,9 +690,9 @@ struct formatter {
|
||||
template <typename FormatContext>
|
||||
auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(
|
||||
specs_.width, specs_.width_ref, ctx, nullptr);
|
||||
specs_.width, specs_.width_ref, ctx);
|
||||
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(
|
||||
specs_.precision, specs_.precision_ref, ctx, nullptr);
|
||||
specs_.precision, specs_.precision_ref, ctx);
|
||||
using range_type = fmt::internal::output_range<typename FormatContext::iterator,
|
||||
typename FormatContext::char_type>;
|
||||
return visit_format_arg(arg_formatter<range_type>(ctx, nullptr, &specs_),
|
||||
@ -718,7 +718,7 @@ template<class... Args>
|
||||
string vformat(string_view fmt, format_args args) {
|
||||
fmt::memory_buffer mbuf;
|
||||
fmt::internal::buffer<char>& buf = mbuf;
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
detail::format_handler<detail::arg_formatter<range>, char, format_context>
|
||||
h(range(std::back_inserter(buf)), fmt, args, {});
|
||||
fmt::internal::parse_format_string<false>(fmt::to_string_view(fmt), h);
|
||||
@ -730,17 +730,17 @@ wstring vformat(wstring_view fmt, wformat_args args);
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, string_view fmt, const Args&... args) {
|
||||
using context = basic_format_context<Out, decltype(fmt)::value_type>;
|
||||
return vformat_to(out, fmt, {make_format_args<context>(args...)});
|
||||
return vformat_to(out, fmt, make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template<class Out, class... Args>
|
||||
Out format_to(Out out, wstring_view fmt, const Args&... args) {
|
||||
using context = basic_format_context<Out, decltype(fmt)::value_type>;
|
||||
return vformat_to(out, fmt, {make_format_args<context>(args...)});
|
||||
return vformat_to(out, fmt, make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<Out, char> args) {
|
||||
Out vformat_to(Out out, string_view fmt, format_args_t<fmt::type_identity_t<Out>, char> args) {
|
||||
using range = fmt::internal::output_range<Out, char>;
|
||||
detail::format_handler<detail::arg_formatter<range>, char, basic_format_context<Out, char>>
|
||||
h(range(out), fmt, args, {});
|
||||
@ -749,7 +749,7 @@ template<class Out>
|
||||
}
|
||||
|
||||
template<class Out>
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<Out, wchar_t> args);
|
||||
Out vformat_to(Out out, wstring_view fmt, format_args_t<fmt::type_identity_t<Out>, wchar_t> args);
|
||||
|
||||
template<class Out, class... Args>
|
||||
format_to_n_result<Out> format_to_n(Out out, iter_difference_t<Out> n,
|
||||
|
6
test/format-dyn-args-test.cc
Normal file
6
test/format-dyn-args-test.cc
Normal file
@ -0,0 +1,6 @@
|
||||
// Copyright (c) 2020 Vladimir Solontsov
|
||||
// SPDX-License-Identifier: MIT Licence
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "gtest-extra.h"
|
@ -10,65 +10,243 @@
|
||||
#include "test-assert.h"
|
||||
|
||||
// Include format.cc instead of format.h to test implementation.
|
||||
#include "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "../src/format.cc"
|
||||
#include "fmt/printf.h"
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
#undef max
|
||||
|
||||
using fmt::internal::bigint;
|
||||
using fmt::internal::fp;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
template <bool is_iec559> void test_construct_from_double() {
|
||||
static_assert(!std::is_copy_constructible<bigint>::value, "");
|
||||
static_assert(!std::is_copy_assignable<bigint>::value, "");
|
||||
|
||||
TEST(BigIntTest, Construct) {
|
||||
EXPECT_EQ("", fmt::format("{}", bigint()));
|
||||
EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
|
||||
EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Compare) {
|
||||
bigint n1(42);
|
||||
bigint n2(42);
|
||||
EXPECT_EQ(compare(n1, n2), 0);
|
||||
n2 <<= 32;
|
||||
EXPECT_LT(compare(n1, n2), 0);
|
||||
bigint n3(43);
|
||||
EXPECT_LT(compare(n1, n3), 0);
|
||||
EXPECT_GT(compare(n3, n1), 0);
|
||||
bigint n4(42 * 0x100000001);
|
||||
EXPECT_LT(compare(n2, n4), 0);
|
||||
EXPECT_GT(compare(n4, n2), 0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, AddCompare) {
|
||||
EXPECT_LT(
|
||||
add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
|
||||
EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
|
||||
EXPECT_GT(add_compare(bigint(1) <<= 32, bigint(0), bigint(0xffffffff)), 0);
|
||||
EXPECT_GT(add_compare(bigint(0), bigint(1) <<= 32, bigint(0xffffffff)), 0);
|
||||
EXPECT_GT(add_compare(bigint(42), bigint(1), bigint(42)), 0);
|
||||
EXPECT_GT(add_compare(bigint(0xffffffff), bigint(1), bigint(0xffffffff)), 0);
|
||||
EXPECT_LT(add_compare(bigint(10), bigint(10), bigint(22)), 0);
|
||||
EXPECT_LT(add_compare(bigint(0x100000010), bigint(0x100000010),
|
||||
bigint(0x300000010)),
|
||||
0);
|
||||
EXPECT_GT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000000)),
|
||||
0);
|
||||
EXPECT_EQ(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000001)),
|
||||
0);
|
||||
EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000002)),
|
||||
0);
|
||||
EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
|
||||
bigint(0x300000003)),
|
||||
0);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, ShiftLeft) {
|
||||
bigint n(0x42);
|
||||
n <<= 0;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n <<= 1;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n <<= 25;
|
||||
EXPECT_EQ("108000000", fmt::format("{}", n));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Multiply) {
|
||||
bigint n(0x42);
|
||||
EXPECT_THROW(n *= 0, assertion_failure);
|
||||
n *= 1;
|
||||
EXPECT_EQ("42", fmt::format("{}", n));
|
||||
n *= 2;
|
||||
EXPECT_EQ("84", fmt::format("{}", n));
|
||||
n *= 0x12345678;
|
||||
EXPECT_EQ("962fc95e0", fmt::format("{}", n));
|
||||
bigint bigmax(max_value<uint32_t>());
|
||||
bigmax *= max_value<uint32_t>();
|
||||
EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
|
||||
bigmax.assign(max_value<uint64_t>());
|
||||
bigmax *= max_value<uint64_t>();
|
||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Accumulator) {
|
||||
fmt::internal::accumulator acc;
|
||||
EXPECT_EQ(acc.lower, 0);
|
||||
EXPECT_EQ(acc.upper, 0);
|
||||
acc.upper = 12;
|
||||
acc.lower = 34;
|
||||
EXPECT_EQ(static_cast<uint32_t>(acc), 34);
|
||||
acc += 56;
|
||||
EXPECT_EQ(acc.lower, 90);
|
||||
acc += fmt::internal::max_value<uint64_t>();
|
||||
EXPECT_EQ(acc.upper, 13);
|
||||
EXPECT_EQ(acc.lower, 89);
|
||||
acc >>= 32;
|
||||
EXPECT_EQ(acc.upper, 0);
|
||||
EXPECT_EQ(acc.lower, 13 * 0x100000000);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, Square) {
|
||||
bigint n0(0);
|
||||
n0.square();
|
||||
EXPECT_EQ("0", fmt::format("{}", n0));
|
||||
bigint n1(0x100);
|
||||
n1.square();
|
||||
EXPECT_EQ("10000", fmt::format("{}", n1));
|
||||
bigint n2(0xfffffffff);
|
||||
n2.square();
|
||||
EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2));
|
||||
bigint n3(max_value<uint64_t>());
|
||||
n3.square();
|
||||
EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3));
|
||||
bigint n4;
|
||||
n4.assign_pow10(10);
|
||||
EXPECT_EQ("2540be400", fmt::format("{}", n4));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignZeroDivisor) {
|
||||
bigint zero(0);
|
||||
EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
|
||||
EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignSelf) {
|
||||
bigint n(100);
|
||||
EXPECT_THROW(n.divmod_assign(n), assertion_failure);
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssignUnaligned) {
|
||||
// (42 << 340) / pow(10, 100):
|
||||
bigint n1(42);
|
||||
n1 <<= 340;
|
||||
bigint n2;
|
||||
n2.assign_pow10(100);
|
||||
int result = n1.divmod_assign(n2);
|
||||
EXPECT_EQ(result, 9406);
|
||||
EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96",
|
||||
fmt::format("{}", n1));
|
||||
}
|
||||
|
||||
TEST(BigIntTest, DivModAssign) {
|
||||
// 100 / 10:
|
||||
bigint n1(100);
|
||||
int result = n1.divmod_assign(bigint(10));
|
||||
EXPECT_EQ(result, 10);
|
||||
EXPECT_EQ("0", fmt::format("{}", n1));
|
||||
// pow(10, 100) / (42 << 320):
|
||||
n1.assign_pow10(100);
|
||||
result = n1.divmod_assign(bigint(42) <<= 320);
|
||||
EXPECT_EQ(result, 111);
|
||||
EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96",
|
||||
fmt::format("{}", n1));
|
||||
// 42 / 100:
|
||||
bigint n2(42);
|
||||
n1.assign_pow10(2);
|
||||
result = n2.divmod_assign(n1);
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ("2a", fmt::format("{}", n2));
|
||||
}
|
||||
|
||||
template <bool is_iec559> void run_double_tests() {
|
||||
fmt::print("warning: double is not IEC559, skipping FP tests\n");
|
||||
}
|
||||
|
||||
template <> void test_construct_from_double<true>() {
|
||||
auto v = fp(1.23);
|
||||
EXPECT_EQ(v.f, 0x13ae147ae147aeu);
|
||||
EXPECT_EQ(v.e, -52);
|
||||
template <> void run_double_tests<true>() {
|
||||
// Construct from double.
|
||||
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, ConstructFromDouble) {
|
||||
test_construct_from_double<std::numeric_limits<double>::is_iec559>();
|
||||
TEST(FPTest, DoubleTests) {
|
||||
run_double_tests<std::numeric_limits<double>::is_iec559>();
|
||||
}
|
||||
|
||||
TEST(FPTest, Normalize) {
|
||||
auto v = fp(0xbeef, 42);
|
||||
v.normalize();
|
||||
EXPECT_EQ(0xbeef000000000000, v.f);
|
||||
EXPECT_EQ(-6, v.e);
|
||||
const auto v = fp(0xbeef, 42);
|
||||
auto normalized = normalize(v);
|
||||
EXPECT_EQ(0xbeef000000000000, normalized.f);
|
||||
EXPECT_EQ(-6, normalized.e);
|
||||
}
|
||||
|
||||
TEST(FPTest, ComputeBoundariesSubnormal) {
|
||||
auto v = fp(0xbeef, 42);
|
||||
fp lower, upper;
|
||||
v.compute_boundaries(lower, upper);
|
||||
EXPECT_EQ(0xbeee800000000000, lower.f);
|
||||
EXPECT_EQ(-6, lower.e);
|
||||
EXPECT_EQ(0xbeef800000000000, upper.f);
|
||||
EXPECT_EQ(-6, upper.e);
|
||||
}
|
||||
|
||||
TEST(FPTest, ComputeBoundaries) {
|
||||
auto v = fp(0x10000000000000, 42);
|
||||
fp lower, upper;
|
||||
v.compute_boundaries(lower, upper);
|
||||
EXPECT_EQ(0x7ffffffffffffe00, lower.f);
|
||||
EXPECT_EQ(31, lower.e);
|
||||
EXPECT_EQ(0x8000000000000400, upper.f);
|
||||
EXPECT_EQ(31, upper.e);
|
||||
}
|
||||
|
||||
TEST(FPTest, Subtract) {
|
||||
auto v = fp(123, 1) - fp(102, 1);
|
||||
EXPECT_EQ(v.f, 21u);
|
||||
EXPECT_EQ(v.e, 1);
|
||||
TEST(FPTest, ComputeFloatBoundaries) {
|
||||
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) {
|
||||
@ -94,25 +272,26 @@ TEST(FPTest, GetCachedPower) {
|
||||
|
||||
TEST(FPTest, GetRoundDirection) {
|
||||
using fmt::internal::get_round_direction;
|
||||
EXPECT_EQ(fmt::internal::down, get_round_direction(100, 50, 0));
|
||||
EXPECT_EQ(fmt::internal::up, get_round_direction(100, 51, 0));
|
||||
EXPECT_EQ(fmt::internal::down, get_round_direction(100, 40, 10));
|
||||
EXPECT_EQ(fmt::internal::up, get_round_direction(100, 60, 10));
|
||||
for (int i = 41; i < 60; ++i)
|
||||
EXPECT_EQ(fmt::internal::unknown, get_round_direction(100, i, 10));
|
||||
uint64_t max = std::numeric_limits<uint64_t>::max();
|
||||
using fmt::internal::round_direction;
|
||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 50, 0));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(100, 51, 0));
|
||||
EXPECT_EQ(round_direction::down, get_round_direction(100, 40, 10));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(100, 60, 10));
|
||||
for (size_t i = 41; i < 60; ++i)
|
||||
EXPECT_EQ(round_direction::unknown, get_round_direction(100, i, 10));
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
|
||||
EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
|
||||
// Check that remainder + error doesn't overflow.
|
||||
EXPECT_EQ(fmt::internal::up, get_round_direction(max, max - 1, 2));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 2));
|
||||
// Check that 2 * (remainder + error) doesn't overflow.
|
||||
EXPECT_EQ(fmt::internal::unknown,
|
||||
EXPECT_EQ(round_direction::unknown,
|
||||
get_round_direction(max, max / 2 + 1, max / 2));
|
||||
// Check that remainder - error doesn't overflow.
|
||||
EXPECT_EQ(fmt::internal::unknown, get_round_direction(100, 40, 41));
|
||||
EXPECT_EQ(round_direction::unknown, get_round_direction(100, 40, 41));
|
||||
// Check that 2 * (remainder - error) doesn't overflow.
|
||||
EXPECT_EQ(fmt::internal::up, get_round_direction(max, max - 1, 1));
|
||||
EXPECT_EQ(round_direction::up, get_round_direction(max, max - 1, 1));
|
||||
}
|
||||
|
||||
TEST(FPTest, FixedHandler) {
|
||||
@ -132,15 +311,14 @@ TEST(FPTest, FixedHandler) {
|
||||
// Check that divisor - error doesn't overflow.
|
||||
EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
|
||||
// Check that 2 * error doesn't overflow.
|
||||
uint64_t max = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t max = max_value<uint64_t>();
|
||||
EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false),
|
||||
digits::error);
|
||||
}
|
||||
|
||||
TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
|
||||
fmt::memory_buffer buf;
|
||||
int exp = 0;
|
||||
grisu_format(4.2f, buf, -1, false, exp);
|
||||
format_float(0.42, -1, fmt::internal::float_specs(), buf);
|
||||
}
|
||||
|
||||
template <typename T> struct value_extractor {
|
||||
@ -149,10 +327,21 @@ template <typename T> struct value_extractor {
|
||||
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::internal::int128_t) {
|
||||
throw std::runtime_error("invalid type __int128_t");
|
||||
}
|
||||
|
||||
FMT_NORETURN T operator()(fmt::internal::uint128_t) {
|
||||
throw std::runtime_error("invalid type __uint128_t");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
TEST(FormatTest, ArgConverter) {
|
||||
long long value = std::numeric_limits<long long>::max();
|
||||
long long value = max_value<long long>();
|
||||
auto arg = fmt::internal::make_arg<fmt::format_context>(value);
|
||||
fmt::visit_format_arg(
|
||||
fmt::internal::arg_converter<long long, fmt::format_context>(arg, 'd'),
|
||||
@ -237,14 +426,19 @@ TEST(FormatTest, FormatErrorCode) {
|
||||
}
|
||||
|
||||
TEST(FormatTest, CountCodePoints) {
|
||||
EXPECT_EQ(4, fmt::internal::count_code_points(fmt::u8string_view("ёжик")));
|
||||
#ifndef __cpp_char8_t
|
||||
using fmt::char8_t;
|
||||
#endif
|
||||
EXPECT_EQ(
|
||||
4, fmt::internal::count_code_points(
|
||||
fmt::basic_string_view<fmt::internal::char8_type>(
|
||||
reinterpret_cast<const fmt::internal::char8_type*>("ёжик"))));
|
||||
}
|
||||
|
||||
// Tests fmt::internal::count_digits for integer type Int.
|
||||
template <typename Int> void test_count_digits() {
|
||||
for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::internal::count_digits(i));
|
||||
for (Int i = 1, n = 1, end = std::numeric_limits<Int>::max() / 10; n <= end;
|
||||
++i) {
|
||||
for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
|
||||
n *= 10;
|
||||
EXPECT_EQ(i, fmt::internal::count_digits(n - 1));
|
||||
EXPECT_EQ(i + 1, fmt::internal::count_digits(n));
|
||||
@ -259,8 +453,8 @@ TEST(UtilTest, CountDigits) {
|
||||
TEST(UtilTest, WriteUIntPtr) {
|
||||
fmt::memory_buffer buf;
|
||||
fmt::internal::writer writer(buf);
|
||||
writer.write_pointer(fmt::internal::bit_cast<fmt::internal::fallback_uintptr>(
|
||||
reinterpret_cast<void*>(0xface)),
|
||||
nullptr);
|
||||
writer.write_pointer(
|
||||
fmt::internal::fallback_uintptr(reinterpret_cast<void*>(0xface)),
|
||||
nullptr);
|
||||
EXPECT_EQ("0xface", to_string(buf));
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cfloat>
|
||||
#include <climits>
|
||||
@ -20,8 +21,14 @@
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
// Check if fmt/format.h compiles with the X11 index macro defined.
|
||||
#define index(x, y) no nice things
|
||||
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#undef index
|
||||
|
||||
#include "gmock.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "mock-allocator.h"
|
||||
@ -34,12 +41,14 @@
|
||||
using std::size_t;
|
||||
|
||||
using fmt::basic_memory_buffer;
|
||||
using fmt::internal::basic_writer;
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
using fmt::memory_buffer;
|
||||
using fmt::string_view;
|
||||
using fmt::wmemory_buffer;
|
||||
using fmt::wstring_view;
|
||||
using fmt::internal::basic_writer;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
@ -99,7 +108,7 @@ void std_format(long double value, std::wstring& result) {
|
||||
template <typename Char, typename T>
|
||||
::testing::AssertionResult check_write(const T& value, const char* type) {
|
||||
fmt::basic_memory_buffer<Char> buffer;
|
||||
using range = fmt::internal::buffer_range<Char>;
|
||||
using range = fmt::buffer_range<Char>;
|
||||
basic_writer<range> writer(buffer);
|
||||
writer.write(value);
|
||||
std::basic_string<Char> actual = to_string(buffer);
|
||||
@ -162,8 +171,7 @@ TEST(UtilTest, Increment) {
|
||||
}
|
||||
|
||||
TEST(UtilTest, ParseNonnegativeInt) {
|
||||
if (std::numeric_limits<int>::max() !=
|
||||
static_cast<int>(static_cast<unsigned>(1) << 31)) {
|
||||
if (max_value<int>() != static_cast<int>(static_cast<unsigned>(1) << 31)) {
|
||||
fmt::print("Skipping parse_nonnegative_int test\n");
|
||||
return;
|
||||
}
|
||||
@ -181,7 +189,7 @@ TEST(UtilTest, ParseNonnegativeInt) {
|
||||
}
|
||||
|
||||
TEST(IteratorTest, CountingIterator) {
|
||||
fmt::internal::counting_iterator<char> it;
|
||||
fmt::internal::counting_iterator it;
|
||||
auto prev = it++;
|
||||
EXPECT_EQ(prev.count(), 0);
|
||||
EXPECT_EQ(it.count(), 1);
|
||||
@ -270,7 +278,7 @@ static void check_move_buffer(
|
||||
EXPECT_EQ(alloc, buffer2.get_allocator().get());
|
||||
}
|
||||
|
||||
TEST(MemoryBufferTest, MoveCtor) {
|
||||
TEST(MemoryBufferTest, MoveCtorInlineBuffer) {
|
||||
std::allocator<char> alloc;
|
||||
basic_memory_buffer<char, 5, TestAllocator> buffer((TestAllocator(&alloc)));
|
||||
const char test[] = "test";
|
||||
@ -280,15 +288,22 @@ TEST(MemoryBufferTest, MoveCtor) {
|
||||
// dynamic allocation.
|
||||
buffer.push_back('a');
|
||||
check_move_buffer("testa", buffer);
|
||||
}
|
||||
|
||||
TEST(MemoryBufferTest, MoveCtorDynamicBuffer) {
|
||||
std::allocator<char> alloc;
|
||||
basic_memory_buffer<char, 4, TestAllocator> buffer((TestAllocator(&alloc)));
|
||||
const char test[] = "test";
|
||||
buffer.append(test, test + 4);
|
||||
const char* inline_buffer_ptr = &buffer[0];
|
||||
// Adding one more character causes the content to move from the inline to
|
||||
// a dynamically allocated buffer.
|
||||
buffer.push_back('b');
|
||||
basic_memory_buffer<char, 5, TestAllocator> buffer2(std::move(buffer));
|
||||
buffer.push_back('a');
|
||||
basic_memory_buffer<char, 4, TestAllocator> buffer2(std::move(buffer));
|
||||
// Move should rip the guts of the first buffer.
|
||||
EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
|
||||
EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
|
||||
EXPECT_GT(buffer2.capacity(), 5u);
|
||||
EXPECT_EQ("testa", std::string(&buffer2[0], buffer2.size()));
|
||||
EXPECT_GT(buffer2.capacity(), 4u);
|
||||
}
|
||||
|
||||
static void check_move_assign_buffer(const char* str,
|
||||
@ -387,26 +402,17 @@ TEST(MemoryBufferTest, ExceptionInDeallocate) {
|
||||
EXPECT_CALL(alloc, deallocate(&mem2[0], 2 * size));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST(UtilTest, UTF16ToUTF8) {
|
||||
std::string s = "ёжик";
|
||||
fmt::internal::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8EmptyString) {
|
||||
std::string s = "";
|
||||
fmt::internal::utf16_to_utf8 u(L"");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF8ToUTF16) {
|
||||
std::string s = "лошадка";
|
||||
fmt::internal::utf8_to_utf16 u(s.c_str());
|
||||
fmt::internal::utf8_to_utf16 u("лошадка");
|
||||
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
|
||||
EXPECT_EQ(7, u.size());
|
||||
// U+10437 { DESERET SMALL LETTER YEE }
|
||||
EXPECT_EQ(L"\xD801\xDC37", fmt::internal::utf8_to_utf16("𐐷").str());
|
||||
EXPECT_THROW_MSG(fmt::internal::utf8_to_utf16("\xc3\x28"), std::runtime_error,
|
||||
"invalid utf8");
|
||||
EXPECT_THROW_MSG(fmt::internal::utf8_to_utf16(fmt::string_view("л", 1)),
|
||||
std::runtime_error, "invalid utf8");
|
||||
EXPECT_EQ(L"123456", fmt::internal::utf8_to_utf16("123456").str());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF8ToUTF16EmptyString) {
|
||||
@ -416,59 +422,6 @@ TEST(UtilTest, UTF8ToUTF16EmptyString) {
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
template <typename Converter, typename Char>
|
||||
void check_utf_conversion_error(
|
||||
const char* message,
|
||||
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
||||
fmt::system_error error(0, "");
|
||||
try {
|
||||
(Converter)(str);
|
||||
} catch (const fmt::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
|
||||
EXPECT_EQ(fmt::to_string(out), error.what());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8Error) {
|
||||
check_utf_conversion_error<fmt::internal::utf16_to_utf8, wchar_t>(
|
||||
"cannot convert string from UTF-16 to UTF-8");
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF8ToUTF16Error) {
|
||||
const char* message = "cannot convert string from UTF-8 to UTF-16";
|
||||
check_utf_conversion_error<fmt::internal::utf8_to_utf16, char>(message);
|
||||
check_utf_conversion_error<fmt::internal::utf8_to_utf16, char>(
|
||||
message, fmt::string_view("foo", INT_MAX + 1u));
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8Convert) {
|
||||
fmt::internal::utf16_to_utf8 u;
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1)));
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
||||
u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u)));
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
typedef void (*FormatErrorMessage)(fmt::internal::buffer<char>& out,
|
||||
int error_code, string_view message);
|
||||
|
||||
template <typename Error>
|
||||
void check_throw_error(int error_code, FormatErrorMessage format) {
|
||||
fmt::system_error error(0, "");
|
||||
try {
|
||||
throw Error(error_code, "test {}", "error");
|
||||
} catch (const fmt::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
fmt::memory_buffer message;
|
||||
format(message, error_code, "test error");
|
||||
EXPECT_EQ(to_string(message), error.what());
|
||||
EXPECT_EQ(error_code, error.error_code());
|
||||
}
|
||||
|
||||
TEST(UtilTest, FormatSystemError) {
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_system_error(message, EDOM, "test");
|
||||
@ -477,7 +430,7 @@ TEST(UtilTest, FormatSystemError) {
|
||||
message = fmt::memory_buffer();
|
||||
|
||||
// Check if std::allocator throws on allocating max size_t / 2 chars.
|
||||
size_t max_size = std::numeric_limits<size_t>::max() / 2;
|
||||
size_t max_size = max_value<size_t>() / 2;
|
||||
bool throws_on_alloc = false;
|
||||
try {
|
||||
std::allocator<char> alloc;
|
||||
@ -497,7 +450,17 @@ TEST(UtilTest, SystemError) {
|
||||
fmt::system_error e(EDOM, "test");
|
||||
EXPECT_EQ(fmt::format("test: {}", get_system_error(EDOM)), e.what());
|
||||
EXPECT_EQ(EDOM, e.error_code());
|
||||
check_throw_error<fmt::system_error>(EDOM, fmt::format_system_error);
|
||||
|
||||
fmt::system_error error(0, "");
|
||||
try {
|
||||
throw fmt::system_error(EDOM, "test {}", "error");
|
||||
} catch (const fmt::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
fmt::memory_buffer message;
|
||||
fmt::format_system_error(message, EDOM, "test error");
|
||||
EXPECT_EQ(to_string(message), error.what());
|
||||
EXPECT_EQ(EDOM, error.error_code());
|
||||
}
|
||||
|
||||
TEST(UtilTest, ReportSystemError) {
|
||||
@ -508,70 +471,6 @@ TEST(UtilTest, ReportSystemError) {
|
||||
to_string(out));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
TEST(UtilTest, FormatWindowsError) {
|
||||
LPWSTR message = 0;
|
||||
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
0, ERROR_FILE_EXISTS,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPWSTR>(&message), 0, 0);
|
||||
fmt::internal::utf16_to_utf8 utf8_message(message);
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS,
|
||||
"test");
|
||||
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
||||
fmt::to_string(actual_message));
|
||||
actual_message.resize(0);
|
||||
fmt::internal::format_windows_error(
|
||||
actual_message, ERROR_FILE_EXISTS,
|
||||
fmt::string_view(0, std::numeric_limits<size_t>::max()));
|
||||
EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
|
||||
fmt::to_string(actual_message));
|
||||
}
|
||||
|
||||
TEST(UtilTest, FormatLongWindowsError) {
|
||||
LPWSTR message = 0;
|
||||
// this error code is not available on all Windows platforms and
|
||||
// Windows SDKs, so do not fail the test if the error string cannot
|
||||
// be retrieved.
|
||||
const int provisioning_not_allowed =
|
||||
0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/;
|
||||
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
0, static_cast<DWORD>(provisioning_not_allowed),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) {
|
||||
return;
|
||||
}
|
||||
fmt::internal::utf16_to_utf8 utf8_message(message);
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::internal::format_windows_error(actual_message, provisioning_not_allowed,
|
||||
"test");
|
||||
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
||||
fmt::to_string(actual_message));
|
||||
}
|
||||
|
||||
TEST(UtilTest, WindowsError) {
|
||||
check_throw_error<fmt::windows_error>(ERROR_FILE_EXISTS,
|
||||
fmt::internal::format_windows_error);
|
||||
}
|
||||
|
||||
TEST(UtilTest, ReportWindowsError) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::internal::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
|
||||
out.push_back('\n');
|
||||
EXPECT_WRITE(stderr,
|
||||
fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"),
|
||||
fmt::to_string(out));
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
TEST(StringViewTest, Ctor) {
|
||||
EXPECT_STREQ("abc", string_view("abc").data());
|
||||
EXPECT_EQ(3u, string_view("abc").size());
|
||||
@ -593,31 +492,31 @@ TEST(WriterTest, WriteInt) {
|
||||
CHECK_WRITE(static_cast<short>(12));
|
||||
CHECK_WRITE(34u);
|
||||
CHECK_WRITE(std::numeric_limits<int>::min());
|
||||
CHECK_WRITE(std::numeric_limits<int>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned>::max());
|
||||
CHECK_WRITE(max_value<int>());
|
||||
CHECK_WRITE(max_value<unsigned>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteLong) {
|
||||
CHECK_WRITE(56l);
|
||||
CHECK_WRITE(78ul);
|
||||
CHECK_WRITE(std::numeric_limits<long>::min());
|
||||
CHECK_WRITE(std::numeric_limits<long>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned long>::max());
|
||||
CHECK_WRITE(max_value<long>());
|
||||
CHECK_WRITE(max_value<unsigned long>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteLongLong) {
|
||||
CHECK_WRITE(56ll);
|
||||
CHECK_WRITE(78ull);
|
||||
CHECK_WRITE(std::numeric_limits<long long>::min());
|
||||
CHECK_WRITE(std::numeric_limits<long long>::max());
|
||||
CHECK_WRITE(std::numeric_limits<unsigned long long>::max());
|
||||
CHECK_WRITE(max_value<long long>());
|
||||
CHECK_WRITE(max_value<unsigned long long>());
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteDouble) {
|
||||
CHECK_WRITE(4.2);
|
||||
CHECK_WRITE(-4.2);
|
||||
auto min = std::numeric_limits<double>::min();
|
||||
auto max = std::numeric_limits<double>::max();
|
||||
auto max = max_value<double>();
|
||||
if (fmt::internal::use_grisu<double>()) {
|
||||
EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
|
||||
EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
|
||||
@ -637,7 +536,7 @@ TEST(WriterTest, WriteLongDouble) {
|
||||
else
|
||||
fmt::print("warning: long double formatting with std::swprintf is broken");
|
||||
auto min = std::numeric_limits<long double>::min();
|
||||
auto max = std::numeric_limits<long double>::max();
|
||||
auto max = max_value<long double>();
|
||||
if (fmt::internal::use_grisu<long double>()) {
|
||||
EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
|
||||
EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
|
||||
@ -673,9 +572,7 @@ TEST(WriterTest, WriteString) {
|
||||
CHECK_WRITE_WCHAR("abc");
|
||||
}
|
||||
|
||||
TEST(WriterTest, WriteWideString) {
|
||||
CHECK_WRITE_WCHAR(L"abc");
|
||||
}
|
||||
TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); }
|
||||
|
||||
TEST(FormatToTest, FormatWithoutArgs) {
|
||||
std::string s;
|
||||
@ -697,12 +594,6 @@ TEST(FormatToTest, WideString) {
|
||||
EXPECT_STREQ(buf.data(), L"42");
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
|
||||
char buffer[16] = {};
|
||||
fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
|
||||
EXPECT_STREQ("+42.0", buffer);
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToMemoryBuffer) {
|
||||
fmt::basic_memory_buffer<char, 100> buffer;
|
||||
fmt::format_to(buffer, "{}", "foo");
|
||||
@ -856,6 +747,7 @@ TEST(FormatterTest, RightAlign) {
|
||||
EXPECT_EQ(" 0xface", format("{0:>8}", reinterpret_cast<void*>(0xface)));
|
||||
}
|
||||
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
TEST(FormatterTest, NumericAlign) {
|
||||
EXPECT_EQ(" 42", format("{0:=4}", 42));
|
||||
EXPECT_EQ("+ 42", format("{0:=+4}", 42));
|
||||
@ -882,6 +774,13 @@ TEST(FormatterTest, NumericAlign) {
|
||||
EXPECT_EQ(" 1.0", fmt::format("{:= }", 1.0));
|
||||
}
|
||||
|
||||
TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
|
||||
char buffer[16] = {};
|
||||
fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
|
||||
EXPECT_STREQ("+42.0", buffer);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(FormatterTest, CenterAlign) {
|
||||
EXPECT_EQ(" 42 ", format("{0:^5}", 42));
|
||||
EXPECT_EQ(" 42 ", format("{0:^5o}", 042));
|
||||
@ -918,6 +817,9 @@ TEST(FormatterTest, Fill) {
|
||||
EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast<void*>(0xface)));
|
||||
EXPECT_EQ("foo=", format("{:}=", "foo"));
|
||||
EXPECT_EQ(std::string("\0\0\0*", 4), format(string_view("{:\0>4}", 6), '*'));
|
||||
EXPECT_EQ("жж42", format("{0:ж>4}", 42));
|
||||
EXPECT_THROW_MSG(format("{:\x80\x80\x80\x80\x80>}", 0), format_error,
|
||||
"invalid fill");
|
||||
}
|
||||
|
||||
TEST(FormatterTest, PlusSign) {
|
||||
@ -1001,6 +903,7 @@ TEST(FormatterTest, HashFlag) {
|
||||
EXPECT_EQ("0x42", format("{0:#x}", 0x42));
|
||||
EXPECT_EQ("0X42", format("{0:#X}", 0x42));
|
||||
EXPECT_EQ("-0x42", format("{0:#x}", -0x42));
|
||||
EXPECT_EQ("0", format("{0:#o}", 0));
|
||||
EXPECT_EQ("042", format("{0:#o}", 042));
|
||||
EXPECT_EQ("-042", format("{0:#o}", -042));
|
||||
EXPECT_EQ("42", format("{0:#}", 42u));
|
||||
@ -1027,6 +930,10 @@ TEST(FormatterTest, HashFlag) {
|
||||
|
||||
EXPECT_EQ("-42.0", format("{0:#}", -42.0));
|
||||
EXPECT_EQ("-42.0", format("{0:#}", -42.0l));
|
||||
EXPECT_EQ("4.e+01", format("{:#.0e}", 42.0));
|
||||
EXPECT_EQ("0.", format("{:#.0f}", 0.01));
|
||||
auto s = format("{:#.0f}", 0.5); // MSVC's printf uses wrong rounding mode.
|
||||
EXPECT_TRUE(s == "0." || s == "1.");
|
||||
EXPECT_THROW_MSG(format("{0:#", 'c'), format_error,
|
||||
"missing '}' in format string");
|
||||
EXPECT_THROW_MSG(format("{0:#}", 'c'), format_error,
|
||||
@ -1082,7 +989,9 @@ TEST(FormatterTest, Width) {
|
||||
EXPECT_EQ(" 0xcafe", format("{0:10}", reinterpret_cast<void*>(0xcafe)));
|
||||
EXPECT_EQ("x ", format("{0:11}", 'x'));
|
||||
EXPECT_EQ("str ", format("{0:12}", "str"));
|
||||
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "**🤡**");
|
||||
}
|
||||
|
||||
template <typename T> inline T const_check(T value) { return value; }
|
||||
|
||||
TEST(FormatterTest, RuntimeWidth) {
|
||||
@ -1185,12 +1094,39 @@ TEST(FormatterTest, Precision) {
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG(format("{0:.2f}", 42ull), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG(format("{0:.2%}", 42), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG(format("{0:3.0}", 'x'), format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_EQ("1.2", format("{0:.2}", 1.2345));
|
||||
EXPECT_EQ("1.2", format("{0:.2}", 1.2345l));
|
||||
EXPECT_EQ("1.2e+56", format("{:.2}", 1.234e56));
|
||||
EXPECT_EQ("1e+00", format("{:.0e}", 1.0L));
|
||||
EXPECT_EQ(" 0.0e+00", format("{:9.1e}", 0.0));
|
||||
EXPECT_EQ(
|
||||
"4.9406564584124654417656879286822137236505980261432476442558568250067550"
|
||||
"727020875186529983636163599237979656469544571773092665671035593979639877"
|
||||
"479601078187812630071319031140452784581716784898210368871863605699873072"
|
||||
"305000638740915356498438731247339727316961514003171538539807412623856559"
|
||||
"117102665855668676818703956031062493194527159149245532930545654440112748"
|
||||
"012970999954193198940908041656332452475714786901472678015935523861155013"
|
||||
"480352649347201937902681071074917033322268447533357208324319361e-324",
|
||||
format("{:.494}", 4.9406564584124654E-324));
|
||||
EXPECT_EQ(
|
||||
"-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||
"000000000000000000000000000000000000000000000000000P+127",
|
||||
format("{:.838A}", -2.14001164E+38));
|
||||
EXPECT_EQ("123.", format("{:#.0f}", 123.0));
|
||||
EXPECT_EQ("1.23", format("{:.02f}", 1.234));
|
||||
EXPECT_EQ("0.001", format("{:.1g}", 0.001));
|
||||
|
||||
EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast<void*>(0xcafe)),
|
||||
format_error,
|
||||
@ -1198,6 +1134,8 @@ TEST(FormatterTest, Precision) {
|
||||
EXPECT_THROW_MSG(format("{0:.2f}", reinterpret_cast<void*>(0xcafe)),
|
||||
format_error,
|
||||
"precision not allowed for this argument type");
|
||||
EXPECT_THROW_MSG(format("{:.{}e}", 42.0, fmt::internal::max_value<int>()),
|
||||
format_error, "number is too big");
|
||||
|
||||
EXPECT_EQ("st", format("{0:.2}", "str"));
|
||||
}
|
||||
@ -1318,7 +1256,7 @@ TEST(FormatterTest, FormatShort) {
|
||||
TEST(FormatterTest, FormatInt) {
|
||||
EXPECT_THROW_MSG(format("{0:v", 42), format_error,
|
||||
"missing '}' in format string");
|
||||
check_unknown_types(42, "bBdoxXn", "integer");
|
||||
check_unknown_types(42, "bBdoxXnL", "integer");
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatBin) {
|
||||
@ -1330,9 +1268,17 @@ TEST(FormatterTest, FormatBin) {
|
||||
EXPECT_EQ("10010001101000101011001111000", format("{0:b}", 0x12345678));
|
||||
EXPECT_EQ("10010000101010111100110111101111", format("{0:b}", 0x90ABCDEF));
|
||||
EXPECT_EQ("11111111111111111111111111111111",
|
||||
format("{0:b}", std::numeric_limits<uint32_t>::max()));
|
||||
format("{0:b}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
#if FMT_USE_INT128
|
||||
constexpr auto int128_max = static_cast<__int128_t>(
|
||||
(static_cast<__uint128_t>(1) << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1);
|
||||
constexpr auto int128_min = -int128_max - 1;
|
||||
|
||||
constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
|
||||
#endif
|
||||
|
||||
TEST(FormatterTest, FormatDec) {
|
||||
EXPECT_EQ("0", format("{0}", 0));
|
||||
EXPECT_EQ("42", format("{0}", 42));
|
||||
@ -1341,6 +1287,23 @@ TEST(FormatterTest, FormatDec) {
|
||||
EXPECT_EQ("-42", format("{0}", -42));
|
||||
EXPECT_EQ("12345", format("{0}", 12345));
|
||||
EXPECT_EQ("67890", format("{0}", 67890));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("9223372036854775808",
|
||||
format("{0}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-9223372036854775809",
|
||||
format("{0}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("18446744073709551616",
|
||||
format("{0}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("170141183460469231731687303715884105727",
|
||||
format("{0}", int128_max));
|
||||
EXPECT_EQ("-170141183460469231731687303715884105728",
|
||||
format("{0}", int128_min));
|
||||
EXPECT_EQ("340282366920938463463374607431768211455",
|
||||
format("{0}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%d", INT_MIN);
|
||||
EXPECT_EQ(buffer, format("{0}", INT_MIN));
|
||||
@ -1365,6 +1328,19 @@ TEST(FormatterTest, FormatHex) {
|
||||
EXPECT_EQ("90abcdef", format("{0:x}", 0x90abcdef));
|
||||
EXPECT_EQ("12345678", format("{0:X}", 0x12345678));
|
||||
EXPECT_EQ("90ABCDEF", format("{0:X}", 0x90ABCDEF));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0:x}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0:x}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("8000000000000000",
|
||||
format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-8000000000000001",
|
||||
format("{0:x}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("10000000000000000",
|
||||
format("{0:x}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("7fffffffffffffffffffffffffffffff", format("{0:x}", int128_max));
|
||||
EXPECT_EQ("-80000000000000000000000000000000", format("{0:x}", int128_min));
|
||||
EXPECT_EQ("ffffffffffffffffffffffffffffffff", format("{0:x}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "-%x", 0 - static_cast<unsigned>(INT_MIN));
|
||||
@ -1387,6 +1363,23 @@ TEST(FormatterTest, FormatOct) {
|
||||
EXPECT_EQ("42", format("{0:o}", 042u));
|
||||
EXPECT_EQ("-42", format("{0:o}", -042));
|
||||
EXPECT_EQ("12345670", format("{0:o}", 012345670));
|
||||
#if FMT_USE_INT128
|
||||
EXPECT_EQ("0", format("{0:o}", static_cast<__int128_t>(0)));
|
||||
EXPECT_EQ("0", format("{0:o}", static_cast<__uint128_t>(0)));
|
||||
EXPECT_EQ("1000000000000000000000",
|
||||
format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1));
|
||||
EXPECT_EQ("-1000000000000000000001",
|
||||
format("{0:o}", static_cast<__int128_t>(INT64_MIN) - 1));
|
||||
EXPECT_EQ("2000000000000000000000",
|
||||
format("{0:o}", static_cast<__int128_t>(UINT64_MAX) + 1));
|
||||
EXPECT_EQ("1777777777777777777777777777777777777777777",
|
||||
format("{0:o}", int128_max));
|
||||
EXPECT_EQ("-2000000000000000000000000000000000000000000",
|
||||
format("{0:o}", int128_min));
|
||||
EXPECT_EQ("3777777777777777777777777777777777777777777",
|
||||
format("{0:o}", uint128_max));
|
||||
#endif
|
||||
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "-%o", 0 - static_cast<unsigned>(INT_MIN));
|
||||
EXPECT_EQ(buffer, format("{0:o}", INT_MIN));
|
||||
@ -1403,11 +1396,8 @@ TEST(FormatterTest, FormatOct) {
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatIntLocale) {
|
||||
EXPECT_EQ("123", format("{:n}", 123));
|
||||
EXPECT_EQ("1,234", format("{:n}", 1234));
|
||||
EXPECT_EQ("1,234,567", format("{:n}", 1234567));
|
||||
EXPECT_EQ("4,294,967,295",
|
||||
format("{:n}", std::numeric_limits<uint32_t>::max()));
|
||||
EXPECT_EQ("1234", format("{:n}", 1234));
|
||||
EXPECT_EQ("1234", format("{:L}", 1234));
|
||||
}
|
||||
|
||||
struct ConvertibleToLongLong {
|
||||
@ -1420,7 +1410,6 @@ TEST(FormatterTest, FormatConvertibleToLongLong) {
|
||||
|
||||
TEST(FormatterTest, FormatFloat) {
|
||||
EXPECT_EQ("392.500000", format("{0:f}", 392.5f));
|
||||
EXPECT_EQ("12.500000%", format("{0:%}", 0.125f));
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatDouble) {
|
||||
@ -1433,8 +1422,6 @@ TEST(FormatterTest, FormatDouble) {
|
||||
EXPECT_EQ("392.65", format("{:G}", 392.65));
|
||||
EXPECT_EQ("392.650000", format("{:f}", 392.65));
|
||||
EXPECT_EQ("392.650000", format("{:F}", 392.65));
|
||||
EXPECT_EQ("12.500000%", format("{:%}", 0.125));
|
||||
EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432));
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%e", 392.65);
|
||||
EXPECT_EQ(buffer, format("{0:e}", 392.65));
|
||||
@ -1474,7 +1461,6 @@ TEST(FormatterTest, FormatNaN) {
|
||||
EXPECT_EQ("nan ", format("{:<7}", nan));
|
||||
EXPECT_EQ(" nan ", format("{:^7}", nan));
|
||||
EXPECT_EQ(" nan", format("{:>7}", nan));
|
||||
EXPECT_EQ("nan%", format("{:%}", nan));
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatInfinity) {
|
||||
@ -1487,7 +1473,6 @@ TEST(FormatterTest, FormatInfinity) {
|
||||
EXPECT_EQ("inf ", format("{:<7}", inf));
|
||||
EXPECT_EQ(" inf ", format("{:^7}", inf));
|
||||
EXPECT_EQ(" inf", format("{:>7}", inf));
|
||||
EXPECT_EQ("inf%", format("{:%}", inf));
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatLongDouble) {
|
||||
@ -1498,8 +1483,6 @@ TEST(FormatterTest, FormatLongDouble) {
|
||||
EXPECT_EQ("392.65", format("{0:G}", 392.65l));
|
||||
EXPECT_EQ("392.650000", format("{0:f}", 392.65l));
|
||||
EXPECT_EQ("392.650000", format("{0:F}", 392.65l));
|
||||
EXPECT_EQ("12.500000%", format("{:%}", 0.125l));
|
||||
EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432l));
|
||||
char buffer[BUFFER_SIZE];
|
||||
safe_sprintf(buffer, "%Le", 392.65l);
|
||||
EXPECT_EQ(buffer, format("{0:e}", 392.65l));
|
||||
@ -1509,7 +1492,7 @@ TEST(FormatterTest, FormatLongDouble) {
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatChar) {
|
||||
const char types[] = "cbBdoxXn";
|
||||
const char types[] = "cbBdoxXnL";
|
||||
check_unknown_types('a', types, "char");
|
||||
EXPECT_EQ("a", format("{0}", 'a'));
|
||||
EXPECT_EQ("z", format("{0:c}", 'z'));
|
||||
@ -1603,6 +1586,26 @@ TEST(FormatterTest, FormatStdStringView) {
|
||||
EXPECT_EQ("test", format("{}", std::string_view("test")));
|
||||
EXPECT_EQ("foo", format("{}", string_viewable()));
|
||||
}
|
||||
|
||||
struct explicitly_convertible_to_std_string_view {
|
||||
explicit operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
namespace fmt {
|
||||
template <>
|
||||
struct formatter<explicitly_convertible_to_std_string_view>
|
||||
: formatter<std::string_view> {
|
||||
auto format(const explicitly_convertible_to_std_string_view& v,
|
||||
format_context& ctx) -> decltype(ctx.out()) {
|
||||
return format_to(ctx.out(), "'{}'", std::string_view(v));
|
||||
}
|
||||
};
|
||||
} // namespace fmt
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
EXPECT_EQ("'foo'",
|
||||
fmt::format("{}", explicitly_convertible_to_std_string_view()));
|
||||
}
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
@ -1745,17 +1748,18 @@ TEST(FormatIntTest, FormatInt) {
|
||||
EXPECT_EQ("42", fmt::format_int(42ull).str());
|
||||
EXPECT_EQ("-42", fmt::format_int(-42ll).str());
|
||||
std::ostringstream os;
|
||||
os << std::numeric_limits<int64_t>::max();
|
||||
EXPECT_EQ(os.str(),
|
||||
fmt::format_int(std::numeric_limits<int64_t>::max()).str());
|
||||
os << max_value<int64_t>();
|
||||
EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
|
||||
}
|
||||
|
||||
TEST(FormatTest, Print) {
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
#if FMT_USE_FCNTL
|
||||
EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!");
|
||||
EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
|
||||
"Don't panic!");
|
||||
#endif
|
||||
// Check that the wide print overload compiles.
|
||||
if (fmt::internal::const_check(false)) fmt::print(L"test");
|
||||
}
|
||||
|
||||
TEST(FormatTest, Variadic) {
|
||||
@ -1771,12 +1775,18 @@ TEST(FormatTest, Dynamic) {
|
||||
args.emplace_back(fmt::internal::make_arg<ctx>(1.5f));
|
||||
|
||||
std::string result = fmt::vformat(
|
||||
"{} and {} and {}", fmt::basic_format_args<ctx>(
|
||||
args.data(), static_cast<unsigned>(args.size())));
|
||||
"{} and {} and {}",
|
||||
fmt::basic_format_args<ctx>(args.data(), static_cast<int>(args.size())));
|
||||
|
||||
EXPECT_EQ("42 and abc1 and 1.5", result);
|
||||
}
|
||||
|
||||
TEST(FormatTest, Bytes) {
|
||||
auto s = fmt::format("{:10}", fmt::bytes("ёжик"));
|
||||
EXPECT_EQ("ёжик ", s);
|
||||
EXPECT_EQ(10, s.size());
|
||||
}
|
||||
|
||||
TEST(FormatTest, JoinArg) {
|
||||
using fmt::join;
|
||||
int v1[3] = {1, 2, 3};
|
||||
@ -1848,17 +1858,32 @@ TEST(FormatTest, UnpackedArgs) {
|
||||
struct string_like {};
|
||||
fmt::string_view to_string_view(string_like) { return "foo"; }
|
||||
|
||||
constexpr char with_null[3] = {'{', '}', '\0'};
|
||||
constexpr char no_null[2] = {'{', '}'};
|
||||
|
||||
TEST(FormatTest, CompileTimeString) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42));
|
||||
EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like()));
|
||||
(void)with_null;
|
||||
(void)no_null;
|
||||
#if __cplusplus >= 201703L
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(with_null), 42));
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(no_null), 42));
|
||||
#endif
|
||||
#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42));
|
||||
EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(FormatTest, CustomFormatCompileTimeString) {
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer()));
|
||||
Answer answer;
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer));
|
||||
const Answer const_answer;
|
||||
char buf[10] = {};
|
||||
fmt::format_to(buf, FMT_STRING("{}"), answer);
|
||||
const Answer const_answer = Answer();
|
||||
EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer));
|
||||
}
|
||||
|
||||
@ -1894,6 +1919,11 @@ TEST(FormatTest, UdlTemplate) {
|
||||
EXPECT_EQ("foo", "foo"_format());
|
||||
EXPECT_EQ(" 42", "{0:10}"_format(42));
|
||||
}
|
||||
|
||||
TEST(FormatTest, UdlPassUserDefinedObjectAsLvalue) {
|
||||
Date date(2015, 10, 21);
|
||||
EXPECT_EQ("2015-10-21", "{}"_format(date));
|
||||
}
|
||||
#endif // FMT_USE_USER_DEFINED_LITERALS
|
||||
|
||||
enum TestEnum { A };
|
||||
@ -1901,22 +1931,29 @@ enum TestEnum { A };
|
||||
TEST(FormatTest, Enum) { EXPECT_EQ("0", fmt::format("{}", A)); }
|
||||
|
||||
TEST(FormatTest, FormatterNotSpecialized) {
|
||||
EXPECT_FALSE((fmt::internal::has_formatter<fmt::formatter<TestEnum>,
|
||||
fmt::format_context>::value));
|
||||
static_assert(
|
||||
!fmt::has_formatter<fmt::formatter<TestEnum>, fmt::format_context>::value,
|
||||
"");
|
||||
}
|
||||
|
||||
#if FMT_HAS_FEATURE(cxx_strong_enums)
|
||||
enum TestFixedEnum : short { B };
|
||||
enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
|
||||
|
||||
TEST(FormatTest, FixedEnum) { EXPECT_EQ("0", fmt::format("{}", B)); }
|
||||
TEST(FormatTest, StrongEnum) {
|
||||
EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value));
|
||||
}
|
||||
#endif
|
||||
|
||||
using buffer_range = fmt::internal::buffer_range<char>;
|
||||
using buffer_range = fmt::buffer_range<char>;
|
||||
|
||||
class mock_arg_formatter
|
||||
: public fmt::internal::arg_formatter_base<buffer_range> {
|
||||
private:
|
||||
#if FMT_USE_INT128
|
||||
MOCK_METHOD1(call, void(__int128_t value));
|
||||
#else
|
||||
MOCK_METHOD1(call, void(long long value));
|
||||
#endif
|
||||
|
||||
public:
|
||||
typedef fmt::internal::arg_formatter_base<buffer_range> base;
|
||||
@ -1929,14 +1966,14 @@ class mock_arg_formatter
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, iterator>::type
|
||||
typename std::enable_if<fmt::internal::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
call(value);
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<!std::is_integral<T>::value, iterator>::type
|
||||
typename std::enable_if<!fmt::internal::is_integral<T>::value, iterator>::type
|
||||
operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
@ -1988,8 +2025,10 @@ TEST(FormatTest, DynamicFormatter) {
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
EXPECT_THROW_MSG(format("{:{0}}", num), format_error,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
EXPECT_THROW_MSG(format("{:=}", str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
#endif
|
||||
EXPECT_THROW_MSG(format("{:+}", str), format_error,
|
||||
"format specifier requires numeric argument");
|
||||
EXPECT_THROW_MSG(format("{:-}", str), format_error,
|
||||
@ -2068,16 +2107,43 @@ TEST(FormatTest, WideFormatToN) {
|
||||
EXPECT_EQ(L"BC x", fmt::wstring_view(buffer, 4));
|
||||
}
|
||||
|
||||
struct test_output_iterator {
|
||||
char* data;
|
||||
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = void;
|
||||
using difference_type = void;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
|
||||
test_output_iterator& operator++() {
|
||||
++data;
|
||||
return *this;
|
||||
}
|
||||
test_output_iterator operator++(int) {
|
||||
auto tmp = *this;
|
||||
++data;
|
||||
return tmp;
|
||||
}
|
||||
char& operator*() { return *data; }
|
||||
};
|
||||
|
||||
TEST(FormatTest, FormatToNOutputIterator) {
|
||||
char buf[10] = {};
|
||||
fmt::format_to_n(test_output_iterator{buf}, 10, "{}", 42);
|
||||
EXPECT_STREQ(buf, "42");
|
||||
}
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
struct test_arg_id_handler {
|
||||
enum result { NONE, EMPTY, INDEX, NAME, ERROR };
|
||||
result res = NONE;
|
||||
unsigned index = 0;
|
||||
int index = 0;
|
||||
string_view name;
|
||||
|
||||
FMT_CONSTEXPR void operator()() { res = EMPTY; }
|
||||
|
||||
FMT_CONSTEXPR void operator()(unsigned i) {
|
||||
FMT_CONSTEXPR void operator()(int i) {
|
||||
res = INDEX;
|
||||
index = i;
|
||||
}
|
||||
@ -2113,9 +2179,9 @@ struct test_format_specs_handler {
|
||||
|
||||
fmt::align_t align = fmt::align::none;
|
||||
char fill = 0;
|
||||
unsigned width = 0;
|
||||
int width = 0;
|
||||
fmt::internal::arg_ref<char> width_ref;
|
||||
unsigned precision = 0;
|
||||
int precision = 0;
|
||||
fmt::internal::arg_ref<char> precision_ref;
|
||||
char type = 0;
|
||||
|
||||
@ -2134,23 +2200,21 @@ struct test_format_specs_handler {
|
||||
type(other.type) {}
|
||||
|
||||
FMT_CONSTEXPR void on_align(fmt::align_t a) { align = a; }
|
||||
FMT_CONSTEXPR void on_fill(char f) { fill = f; }
|
||||
FMT_CONSTEXPR void on_fill(fmt::string_view f) { fill = f[0]; }
|
||||
FMT_CONSTEXPR void on_plus() { res = PLUS; }
|
||||
FMT_CONSTEXPR void on_minus() { res = MINUS; }
|
||||
FMT_CONSTEXPR void on_space() { res = SPACE; }
|
||||
FMT_CONSTEXPR void on_hash() { res = HASH; }
|
||||
FMT_CONSTEXPR void on_zero() { res = ZERO; }
|
||||
|
||||
FMT_CONSTEXPR void on_width(unsigned w) { width = w; }
|
||||
FMT_CONSTEXPR void on_width(int w) { width = w; }
|
||||
FMT_CONSTEXPR void on_dynamic_width(fmt::internal::auto_id) {}
|
||||
FMT_CONSTEXPR void on_dynamic_width(unsigned index) { width_ref = index; }
|
||||
FMT_CONSTEXPR void on_dynamic_width(int index) { width_ref = index; }
|
||||
FMT_CONSTEXPR void on_dynamic_width(string_view) {}
|
||||
|
||||
FMT_CONSTEXPR void on_precision(unsigned p) { precision = p; }
|
||||
FMT_CONSTEXPR void on_precision(int p) { precision = p; }
|
||||
FMT_CONSTEXPR void on_dynamic_precision(fmt::internal::auto_id) {}
|
||||
FMT_CONSTEXPR void on_dynamic_precision(unsigned index) {
|
||||
precision_ref = index;
|
||||
}
|
||||
FMT_CONSTEXPR void on_dynamic_precision(int index) { precision_ref = index; }
|
||||
FMT_CONSTEXPR void on_dynamic_precision(string_view) {}
|
||||
|
||||
FMT_CONSTEXPR void end_precision() {}
|
||||
@ -2185,7 +2249,7 @@ TEST(FormatTest, ConstexprParseFormatSpecs) {
|
||||
struct test_parse_context {
|
||||
typedef char char_type;
|
||||
|
||||
FMT_CONSTEXPR unsigned next_arg_id() { return 11; }
|
||||
FMT_CONSTEXPR int next_arg_id() { return 11; }
|
||||
template <typename Id> FMT_CONSTEXPR void check_arg_id(Id) {}
|
||||
|
||||
FMT_CONSTEXPR const char* begin() { return nullptr; }
|
||||
@ -2214,9 +2278,9 @@ struct test_context {
|
||||
|
||||
template <size_t N>
|
||||
FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) {
|
||||
fmt::format_specs specs;
|
||||
test_parse_context parse_ctx;
|
||||
test_context ctx{};
|
||||
auto specs = fmt::format_specs();
|
||||
auto parse_ctx = test_parse_context();
|
||||
auto ctx = test_context();
|
||||
fmt::internal::specs_handler<test_parse_context, test_context> h(
|
||||
specs, parse_ctx, ctx);
|
||||
parse_format_specs(s, s + N, h);
|
||||
@ -2270,7 +2334,7 @@ TEST(FormatTest, ConstexprDynamicSpecsHandler) {
|
||||
template <size_t N>
|
||||
FMT_CONSTEXPR test_format_specs_handler check_specs(const char (&s)[N]) {
|
||||
fmt::internal::specs_checker<test_format_specs_handler> checker(
|
||||
test_format_specs_handler(), fmt::internal::double_type);
|
||||
test_format_specs_handler(), fmt::internal::type::double_type);
|
||||
parse_format_specs(s, s + N, checker);
|
||||
return checker;
|
||||
}
|
||||
@ -2372,13 +2436,15 @@ TEST(FormatTest, FormatStringErrors) {
|
||||
EXPECT_ERROR("{0:s", "unknown format specifier", Date);
|
||||
# if FMT_MSC_VER >= 1916
|
||||
// This causes an internal compiler error in MSVC2017.
|
||||
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
|
||||
EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
|
||||
EXPECT_ERROR("{:10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR("{:.10000000000}", "number is too big", int);
|
||||
EXPECT_ERROR_NOARGS("{:x}", "argument index out of range");
|
||||
# if FMT_NUMERIC_ALIGN
|
||||
EXPECT_ERROR("{0:=5", "unknown format specifier", int);
|
||||
EXPECT_ERROR("{:=}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
# endif
|
||||
EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
|
||||
const char*);
|
||||
EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
|
||||
@ -2457,48 +2523,8 @@ TEST(FormatTest, FmtStringInTemplate) {
|
||||
|
||||
#endif // FMT_USE_CONSTEXPR
|
||||
|
||||
// C++20 feature test, since r346892 Clang considers char8_t a fundamental
|
||||
// type in this mode. If this is the case __cpp_char8_t will be defined.
|
||||
#ifndef __cpp_char8_t
|
||||
// Locally provide type char8_t defined in format.h
|
||||
using fmt::char8_t;
|
||||
#endif
|
||||
|
||||
TEST(FormatTest, ConstructU8StringViewFromCString) {
|
||||
fmt::u8string_view s("ab");
|
||||
EXPECT_EQ(s.size(), 2u);
|
||||
const char8_t* data = s.data();
|
||||
EXPECT_EQ(data[0], 'a');
|
||||
EXPECT_EQ(data[1], 'b');
|
||||
}
|
||||
|
||||
TEST(FormatTest, ConstructU8StringViewFromDataAndSize) {
|
||||
fmt::u8string_view s("foobar", 3);
|
||||
EXPECT_EQ(s.size(), 3u);
|
||||
const char8_t* data = s.data();
|
||||
EXPECT_EQ(data[0], 'f');
|
||||
EXPECT_EQ(data[1], 'o');
|
||||
EXPECT_EQ(data[2], 'o');
|
||||
}
|
||||
|
||||
#if FMT_USE_USER_DEFINED_LITERALS
|
||||
TEST(FormatTest, U8StringViewLiteral) {
|
||||
using namespace fmt::literals;
|
||||
fmt::u8string_view s = "ab"_u;
|
||||
EXPECT_EQ(s.size(), 2u);
|
||||
const char8_t* data = s.data();
|
||||
EXPECT_EQ(data[0], 'a');
|
||||
EXPECT_EQ(data[1], 'b');
|
||||
EXPECT_EQ(format("{:*^5}"_u, "🤡"_u), "**🤡**"_u);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(FormatTest, FormatU8String) {
|
||||
EXPECT_EQ(format(fmt::u8string_view("{}"), 42), fmt::u8string_view("42"));
|
||||
}
|
||||
|
||||
TEST(FormatTest, EmphasisNonHeaderOnly) {
|
||||
// ensure this compiles even if FMT_HEADER_ONLY is not defined.
|
||||
// Ensure this compiles even if FMT_HEADER_ONLY is not defined.
|
||||
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
|
||||
"\x1b[1mbold error\x1b[0m");
|
||||
}
|
||||
@ -2534,3 +2560,21 @@ TEST(FormatTest, FormatCustomChar) {
|
||||
EXPECT_EQ(result.size(), 1);
|
||||
EXPECT_EQ(result[0], mychar('x'));
|
||||
}
|
||||
|
||||
// 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(FormatTest, FormatUTF8Precision) {
|
||||
using str_type = std::basic_string<fmt::internal::char8_type>;
|
||||
str_type format(
|
||||
reinterpret_cast<const fmt::internal::char8_type*>(u8"{:.4}"));
|
||||
str_type str(reinterpret_cast<const fmt::internal::char8_type*>(
|
||||
u8"caf\u00e9s")); // cafés
|
||||
auto result = fmt::format(format, str);
|
||||
EXPECT_EQ(fmt::internal::count_code_points(result), 4);
|
||||
EXPECT_EQ(result.size(), 5);
|
||||
EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5)));
|
||||
}
|
||||
|
@ -48,10 +48,28 @@ TEST(GrisuTest, Prettify) {
|
||||
EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
|
||||
EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
|
||||
EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10));
|
||||
EXPECT_EQ("1e+11", fmt::format("{}", 1e11));
|
||||
EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11));
|
||||
EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
|
||||
EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
|
||||
EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
|
||||
EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
|
||||
EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
|
||||
}
|
||||
|
||||
TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
|
||||
|
||||
TEST(GrisuTest, Fallback) {
|
||||
EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
|
||||
EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
|
||||
EXPECT_EQ("5.423717798060526e+125",
|
||||
fmt::format("{}", 5.423717798060526e+125));
|
||||
EXPECT_EQ("1.372371880954233e-288",
|
||||
fmt::format("{}", 1.372371880954233e-288));
|
||||
EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244));
|
||||
EXPECT_EQ("2.2506787569811123e-253",
|
||||
fmt::format("{}", 2.2506787569811123e-253));
|
||||
EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8));
|
||||
// pow(2, -25) - assymetric boundaries:
|
||||
EXPECT_EQ("2.9802322387695312e-08",
|
||||
fmt::format("{}", 2.9802322387695312e-08));
|
||||
}
|
||||
|
@ -74,14 +74,6 @@ TEST_F(SingleEvaluationTest, FailedEXPECT_SYSTEM_ERROR) {
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
}
|
||||
|
||||
// Tests that when EXPECT_WRITE fails, it evaluates its message argument
|
||||
// exactly once.
|
||||
TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
|
||||
"01234");
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
}
|
||||
|
||||
// Tests that assertion arguments are evaluated exactly once.
|
||||
TEST_F(SingleEvaluationTest, ExceptionTests) {
|
||||
// successful EXPECT_THROW_MSG
|
||||
@ -163,6 +155,15 @@ TEST_F(SingleEvaluationTest, SystemErrorTests) {
|
||||
EXPECT_EQ(4, b_);
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// Tests that when EXPECT_WRITE fails, it evaluates its message argument
|
||||
// exactly once.
|
||||
TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
|
||||
"01234");
|
||||
EXPECT_EQ(s_ + 1, p_);
|
||||
}
|
||||
|
||||
// Tests that assertion arguments are evaluated exactly once.
|
||||
TEST_F(SingleEvaluationTest, WriteTests) {
|
||||
// successful EXPECT_WRITE
|
||||
@ -187,6 +188,24 @@ TEST_F(SingleEvaluationTest, WriteTests) {
|
||||
EXPECT_EQ(2, b_);
|
||||
}
|
||||
|
||||
// Tests EXPECT_WRITE.
|
||||
TEST(ExpectTest, EXPECT_WRITE) {
|
||||
EXPECT_WRITE(stdout, do_nothing(), "");
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test");
|
||||
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"),
|
||||
"Expected: this\n"
|
||||
" Actual: that");
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
|
||||
<< "expected failure",
|
||||
"expected failure");
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
// Tests that the compiler will not complain about unreachable code in the
|
||||
// EXPECT_THROW_MSG macro.
|
||||
TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
|
||||
@ -280,16 +299,6 @@ TEST(ExpectTest, EXPECT_SYSTEM_ERROR) {
|
||||
format_system_error(EDOM, "test")));
|
||||
}
|
||||
|
||||
// Tests EXPECT_WRITE.
|
||||
TEST(ExpectTest, EXPECT_WRITE) {
|
||||
EXPECT_WRITE(stdout, do_nothing(), "");
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test");
|
||||
EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"),
|
||||
"Expected: this\n"
|
||||
" Actual: that");
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
|
||||
EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
|
||||
<< "unexpected failure";
|
||||
@ -308,20 +317,13 @@ TEST(StreamingAssertionsTest, EXPECT_SYSTEM_ERROR) {
|
||||
"expected failure");
|
||||
}
|
||||
|
||||
TEST(StreamingAssertionsTest, EXPECT_WRITE) {
|
||||
EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
|
||||
EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
|
||||
<< "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_FILE_DESCRIPTORS
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
#include "gtest-extra.h"
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
using fmt::file;
|
||||
|
||||
@ -78,7 +78,7 @@ std::string read(file& f, std::size_t count) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
std::string format_system_error(int error_code, fmt::string_view message) {
|
||||
fmt::memory_buffer out;
|
||||
|
@ -10,16 +10,7 @@
|
||||
|
||||
#include <string>
|
||||
#include "gmock.h"
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#ifndef FMT_USE_FILE_DESCRIPTORS
|
||||
# define FMT_USE_FILE_DESCRIPTORS 0
|
||||
#endif
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
# include "fmt/posix.h"
|
||||
#endif
|
||||
#include "fmt/os.h"
|
||||
|
||||
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
@ -65,7 +56,7 @@ std::string format_system_error(int error_code, fmt::string_view message);
|
||||
EXPECT_THROW_MSG(statement, fmt::system_error, \
|
||||
format_system_error(error_code, message))
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
// Captures file output by redirecting it to a pipe.
|
||||
// The output it can handle is limited by the pipe capacity.
|
||||
@ -151,7 +142,9 @@ std::string read(fmt::file& f, std::size_t count);
|
||||
# define EXPECT_READ(file, expected_content) \
|
||||
EXPECT_EQ(expected_content, read(file, std::strlen(expected_content)))
|
||||
|
||||
#endif // FMT_USE_FILE_DESCRIPTORS
|
||||
#else
|
||||
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
|
||||
ScopedMock() { Mock::instance = this; }
|
||||
|
@ -8,45 +8,82 @@
|
||||
#include "fmt/locale.h"
|
||||
#include "gmock.h"
|
||||
|
||||
using fmt::internal::max_value;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template <typename Char> struct numpunct : std::numpunct<Char> {
|
||||
protected:
|
||||
Char do_decimal_point() const FMT_OVERRIDE { return '?'; }
|
||||
std::string do_grouping() const FMT_OVERRIDE { return "\03"; }
|
||||
Char do_thousands_sep() const FMT_OVERRIDE { return '~'; }
|
||||
};
|
||||
|
||||
template <typename Char> struct no_grouping : std::numpunct<Char> {
|
||||
protected:
|
||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
||||
std::string do_grouping() const FMT_OVERRIDE { return ""; }
|
||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
||||
};
|
||||
|
||||
template <typename Char> struct special_grouping : std::numpunct<Char> {
|
||||
protected:
|
||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
||||
std::string do_grouping() const FMT_OVERRIDE { return "\03\02"; }
|
||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
||||
};
|
||||
|
||||
template <typename Char> struct small_grouping : std::numpunct<Char> {
|
||||
protected:
|
||||
Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
|
||||
std::string do_grouping() const FMT_OVERRIDE { return "\01"; }
|
||||
Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
|
||||
};
|
||||
|
||||
TEST(LocaleTest, DoubleDecimalPoint) {
|
||||
std::locale loc(std::locale(), new numpunct<char>());
|
||||
EXPECT_EQ("1?23", fmt::format(loc, "{:n}", 1.23));
|
||||
// Test with Grisu disabled.
|
||||
fmt::memory_buffer buf;
|
||||
fmt::internal::writer w(buf, fmt::internal::locale_ref(loc));
|
||||
auto specs = fmt::format_specs();
|
||||
specs.type = 'n';
|
||||
w.write_double<double, false>(1.23, specs);
|
||||
EXPECT_EQ(fmt::to_string(buf), "1?23");
|
||||
}
|
||||
|
||||
TEST(LocaleTest, Format) {
|
||||
std::locale loc(std::locale(), new numpunct<char>());
|
||||
EXPECT_EQ("1,234,567", fmt::format(std::locale(), "{:n}", 1234567));
|
||||
EXPECT_EQ("1234567", fmt::format(std::locale(), "{:n}", 1234567));
|
||||
EXPECT_EQ("1~234~567", fmt::format(loc, "{:n}", 1234567));
|
||||
fmt::format_arg_store<fmt::format_context, int> as{1234567};
|
||||
EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:n}", fmt::format_args(as)));
|
||||
std::string s;
|
||||
fmt::format_to(std::back_inserter(s), loc, "{:n}", 1234567);
|
||||
EXPECT_EQ("1~234~567", s);
|
||||
|
||||
std::locale no_grouping_loc(std::locale(), new no_grouping<char>());
|
||||
EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:n}", 1234567));
|
||||
|
||||
std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
|
||||
EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:n}", 12345678));
|
||||
|
||||
std::locale small_grouping_loc(std::locale(), new small_grouping<char>());
|
||||
EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
|
||||
fmt::format(small_grouping_loc, "{:n}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
TEST(LocaleTest, WFormat) {
|
||||
std::locale loc(std::locale(), new numpunct<wchar_t>());
|
||||
EXPECT_EQ(L"1,234,567", fmt::format(std::locale(), L"{:n}", 1234567));
|
||||
EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:n}", 1234567));
|
||||
EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:n}", 1234567));
|
||||
fmt::format_arg_store<fmt::wformat_context, int> as{1234567};
|
||||
EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:n}", fmt::wformat_args(as)));
|
||||
auto sep =
|
||||
std::use_facet<std::numpunct<wchar_t>>(std::locale("C")).thousands_sep();
|
||||
auto result = sep == ',' ? L"1,234,567" : L"1234567";
|
||||
EXPECT_EQ(result, fmt::format(std::locale("C"), L"{:n}", 1234567));
|
||||
EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:n}", 1234567));
|
||||
|
||||
std::locale no_grouping_loc(std::locale(), new no_grouping<wchar_t>());
|
||||
EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:n}", 1234567));
|
||||
|
||||
std::locale special_grouping_loc(std::locale(),
|
||||
new special_grouping<wchar_t>());
|
||||
EXPECT_EQ(L"1,23,45,678",
|
||||
fmt::format(special_grouping_loc, L"{:n}", 12345678));
|
||||
|
||||
std::locale small_grouping_loc(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"{:n}", max_value<uint32_t>()));
|
||||
}
|
||||
|
||||
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Formatting library for C++ - tests of the C++ interface to POSIX functions
|
||||
// Formatting library for C++ - tests of the OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@ -9,7 +9,7 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "fmt/posix.h"
|
||||
#include "fmt/os.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
@ -19,6 +19,125 @@
|
||||
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
# include <windows.h>
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8) {
|
||||
std::string s = "ёжик";
|
||||
fmt::internal::utf16_to_utf8 u(L"\x0451\x0436\x0438\x043A");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8EmptyString) {
|
||||
std::string s = "";
|
||||
fmt::internal::utf16_to_utf8 u(L"");
|
||||
EXPECT_EQ(s, u.str());
|
||||
EXPECT_EQ(s.size(), u.size());
|
||||
}
|
||||
|
||||
template <typename Converter, typename Char>
|
||||
void check_utf_conversion_error(
|
||||
const char* message,
|
||||
fmt::basic_string_view<Char> str = fmt::basic_string_view<Char>(0, 1)) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::internal::format_windows_error(out, ERROR_INVALID_PARAMETER, message);
|
||||
fmt::system_error error(0, "");
|
||||
try {
|
||||
(Converter)(str);
|
||||
} catch (const fmt::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, error.error_code());
|
||||
EXPECT_EQ(fmt::to_string(out), error.what());
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8Error) {
|
||||
check_utf_conversion_error<fmt::internal::utf16_to_utf8, wchar_t>(
|
||||
"cannot convert string from UTF-16 to UTF-8");
|
||||
}
|
||||
|
||||
TEST(UtilTest, UTF16ToUTF8Convert) {
|
||||
fmt::internal::utf16_to_utf8 u;
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER, u.convert(fmt::wstring_view(0, 1)));
|
||||
EXPECT_EQ(ERROR_INVALID_PARAMETER,
|
||||
u.convert(fmt::wstring_view(L"foo", INT_MAX + 1u)));
|
||||
}
|
||||
|
||||
TEST(UtilTest, FormatWindowsError) {
|
||||
LPWSTR message = 0;
|
||||
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
0, ERROR_FILE_EXISTS,
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPWSTR>(&message), 0, 0);
|
||||
fmt::internal::utf16_to_utf8 utf8_message(message);
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS,
|
||||
"test");
|
||||
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
||||
fmt::to_string(actual_message));
|
||||
actual_message.resize(0);
|
||||
auto max_size = fmt::internal::max_value<size_t>();
|
||||
fmt::internal::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) {
|
||||
LPWSTR message = 0;
|
||||
// this error code is not available on all Windows platforms and
|
||||
// Windows SDKs, so do not fail the test if the error string cannot
|
||||
// be retrieved.
|
||||
const int provisioning_not_allowed =
|
||||
0x80284013L /*TBS_E_PROVISIONING_NOT_ALLOWED*/;
|
||||
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
0, static_cast<DWORD>(provisioning_not_allowed),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPWSTR>(&message), 0, 0) == 0) {
|
||||
return;
|
||||
}
|
||||
fmt::internal::utf16_to_utf8 utf8_message(message);
|
||||
LocalFree(message);
|
||||
fmt::memory_buffer actual_message;
|
||||
fmt::internal::format_windows_error(actual_message, provisioning_not_allowed,
|
||||
"test");
|
||||
EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
|
||||
fmt::to_string(actual_message));
|
||||
}
|
||||
|
||||
TEST(UtilTest, WindowsError) {
|
||||
fmt::system_error error(0, "");
|
||||
try {
|
||||
throw fmt::windows_error(ERROR_FILE_EXISTS, "test {}", "error");
|
||||
} catch (const fmt::system_error& e) {
|
||||
error = e;
|
||||
}
|
||||
fmt::memory_buffer message;
|
||||
fmt::internal::format_windows_error(message, ERROR_FILE_EXISTS, "test error");
|
||||
EXPECT_EQ(to_string(message), error.what());
|
||||
EXPECT_EQ(ERROR_FILE_EXISTS, error.error_code());
|
||||
}
|
||||
|
||||
TEST(UtilTest, ReportWindowsError) {
|
||||
fmt::memory_buffer out;
|
||||
fmt::internal::format_windows_error(out, ERROR_FILE_EXISTS, "test error");
|
||||
out.push_back('\n');
|
||||
EXPECT_WRITE(stderr,
|
||||
fmt::report_windows_error(ERROR_FILE_EXISTS, "test error"),
|
||||
fmt::to_string(out));
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
using fmt::file;
|
||||
|
||||
// Checks if the file is open by reading one character from it.
|
||||
@ -148,7 +267,7 @@ TEST(BufferedFileTest, CloseError) {
|
||||
|
||||
TEST(BufferedFileTest, Fileno) {
|
||||
buffered_file f;
|
||||
#ifndef __COVERITY__
|
||||
# ifndef __COVERITY__
|
||||
// fileno on a null FILE pointer either crashes or returns an error.
|
||||
// Disable Coverity because this is intentional.
|
||||
EXPECT_DEATH_IF_SUPPORTED(
|
||||
@ -160,7 +279,7 @@ TEST(BufferedFileTest, Fileno) {
|
||||
}
|
||||
},
|
||||
"");
|
||||
#endif
|
||||
# endif
|
||||
f = open_buffered_file();
|
||||
EXPECT_TRUE(f.fileno() != -1);
|
||||
file copy = file::dup(f.fileno());
|
||||
@ -312,13 +431,13 @@ TEST(FileTest, Dup) {
|
||||
EXPECT_EQ(FILE_CONTENT, read(copy, std::strlen(FILE_CONTENT)));
|
||||
}
|
||||
|
||||
#ifndef __COVERITY__
|
||||
# ifndef __COVERITY__
|
||||
TEST(FileTest, DupError) {
|
||||
int value = -1;
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(file::dup(value), EBADF,
|
||||
"cannot duplicate file descriptor -1");
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
|
||||
TEST(FileTest, Dup2) {
|
||||
file f = open_file();
|
||||
@ -368,17 +487,12 @@ TEST(FileTest, Fdopen) {
|
||||
EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
|
||||
}
|
||||
|
||||
TEST(FileTest, FdopenError) {
|
||||
file f;
|
||||
EXPECT_SYSTEM_ERROR_NOASSERT(f.fdopen("r"), EBADF,
|
||||
"cannot associate stream with file descriptor");
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
# ifdef FMT_LOCALE
|
||||
TEST(LocaleTest, Strtod) {
|
||||
fmt::Locale locale;
|
||||
fmt::locale loc;
|
||||
const char *start = "4.2", *ptr = start;
|
||||
EXPECT_EQ(4.2, locale.strtod(ptr));
|
||||
EXPECT_EQ(4.2, loc.strtod(ptr));
|
||||
EXPECT_EQ(start + 3, ptr);
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
#endif // FMT_USE_FCNTL
|
@ -64,7 +64,7 @@ TEST(OStreamTest, Enum) {
|
||||
EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
|
||||
}
|
||||
|
||||
using range = fmt::internal::buffer_range<char>;
|
||||
using range = fmt::buffer_range<char>;
|
||||
|
||||
struct test_arg_formatter : fmt::arg_formatter<range> {
|
||||
fmt::format_parse_context parse_ctx;
|
||||
@ -95,8 +95,10 @@ TEST(OStreamTest, Format) {
|
||||
TEST(OStreamTest, FormatSpecs) {
|
||||
EXPECT_EQ("def ", format("{0:<5}", TestString("def")));
|
||||
EXPECT_EQ(" def", format("{0:>5}", TestString("def")));
|
||||
#if FMT_NUMERIC_ALIGN
|
||||
EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error,
|
||||
"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,
|
||||
@ -143,8 +145,8 @@ TEST(OStreamTest, WriteToOStream) {
|
||||
}
|
||||
|
||||
TEST(OStreamTest, WriteToOStreamMaxSize) {
|
||||
std::size_t max_size = std::numeric_limits<std::size_t>::max();
|
||||
std::streamsize max_streamsize = std::numeric_limits<std::streamsize>::max();
|
||||
std::size_t max_size = fmt::internal::max_value<std::size_t>();
|
||||
std::streamsize max_streamsize = fmt::internal::max_value<std::streamsize>();
|
||||
if (max_size <= fmt::internal::to_unsigned(max_streamsize)) return;
|
||||
|
||||
struct test_buffer : fmt::internal::buffer<char> {
|
||||
@ -185,8 +187,8 @@ TEST(OStreamTest, Join) {
|
||||
|
||||
#if FMT_USE_CONSTEXPR
|
||||
TEST(OStreamTest, ConstexprString) {
|
||||
EXPECT_EQ("42", format(fmt("{}"), std::string("42")));
|
||||
EXPECT_EQ("a string", format(fmt("{0}"), TestString("a string")));
|
||||
EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
|
||||
EXPECT_EQ("a string", format(FMT_STRING("{0}"), TestString("a string")));
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -240,3 +242,51 @@ TEST(FormatTest, UDL) {
|
||||
EXPECT_EQ("{}"_format("test"), "test");
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename T> struct convertible {
|
||||
T value;
|
||||
explicit convertible(const T& val) : value(val) {}
|
||||
operator T() const { return value; }
|
||||
};
|
||||
|
||||
TEST(OStreamTest, DisableBuiltinOStreamOperators) {
|
||||
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")));
|
||||
}
|
||||
|
||||
struct explicitly_convertible_to_string_like {
|
||||
template <typename String,
|
||||
typename = typename std::enable_if<std::is_constructible<
|
||||
String, const char*, std::size_t>::value>::type>
|
||||
explicit operator String() const {
|
||||
return String("foo", 3u);
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
explicitly_convertible_to_string_like) {
|
||||
return os << "bar";
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) {
|
||||
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
||||
}
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct explicitly_convertible_to_std_string_view {
|
||||
explicit operator fmt::internal::std_string_view<char>() const {
|
||||
return {"foo", 3u};
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
explicitly_convertible_to_std_string_view) {
|
||||
return os << "bar";
|
||||
}
|
||||
|
||||
TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
|
||||
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
|
||||
}
|
||||
|
||||
#endif // FMT_USE_STRING_VIEW
|
||||
|
@ -11,13 +11,15 @@
|
||||
#endif
|
||||
|
||||
#include "posix-mock.h"
|
||||
#include "../src/posix.cc"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <climits>
|
||||
#include <memory>
|
||||
|
||||
#include "../src/os.cc"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <io.h>
|
||||
# undef max
|
||||
@ -30,7 +32,6 @@
|
||||
|
||||
using fmt::buffered_file;
|
||||
using fmt::error_code;
|
||||
using fmt::file;
|
||||
|
||||
using testing::_;
|
||||
using testing::Return;
|
||||
@ -52,7 +53,7 @@ std::size_t read_nbyte;
|
||||
std::size_t write_nbyte;
|
||||
bool sysconf_error;
|
||||
|
||||
enum FStatSimulation { NONE, MAX_SIZE, ERROR } fstat_sim;
|
||||
enum { NONE, MAX_SIZE, ERROR } fstat_sim;
|
||||
} // namespace
|
||||
|
||||
#define EMULATE_EINTR(func, error_result) \
|
||||
@ -198,29 +199,32 @@ static void write_file(fmt::cstring_view filename, fmt::string_view content) {
|
||||
f.print("{}", content);
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
using fmt::file;
|
||||
|
||||
TEST(UtilTest, GetPageSize) {
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si = {};
|
||||
GetSystemInfo(&si);
|
||||
EXPECT_EQ(si.dwPageSize, fmt::getpagesize());
|
||||
#else
|
||||
# else
|
||||
EXPECT_EQ(sysconf(_SC_PAGESIZE), fmt::getpagesize());
|
||||
sysconf_error = true;
|
||||
EXPECT_SYSTEM_ERROR(fmt::getpagesize(), EINVAL,
|
||||
"cannot get memory page size");
|
||||
sysconf_error = false;
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(FileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
write_file("temp", "there must be something here");
|
||||
std::unique_ptr<file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new file("test", file::RDONLY)), open,
|
||||
"cannot open file test");
|
||||
#ifndef _WIN32
|
||||
EXPECT_RETRY(f.reset(new file("temp", file::RDONLY)), open,
|
||||
"cannot open file temp");
|
||||
# ifndef _WIN32
|
||||
char c = 0;
|
||||
f->read(&c, 1);
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(FileTest, CloseNoRetryInDtor) {
|
||||
@ -228,14 +232,15 @@ TEST(FileTest, CloseNoRetryInDtor) {
|
||||
file::pipe(read_end, write_end);
|
||||
std::unique_ptr<file> f(new file(std::move(read_end)));
|
||||
int saved_close_count = 0;
|
||||
EXPECT_WRITE(stderr,
|
||||
{
|
||||
close_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_close_count = close_count;
|
||||
close_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_WRITE(
|
||||
stderr,
|
||||
{
|
||||
close_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_close_count = close_count;
|
||||
close_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_EQ(2, saved_close_count);
|
||||
}
|
||||
|
||||
@ -250,26 +255,26 @@ TEST(FileTest, CloseNoRetry) {
|
||||
|
||||
TEST(FileTest, Size) {
|
||||
std::string content = "top secret, destroy before reading";
|
||||
write_file("test", content);
|
||||
file f("test", file::RDONLY);
|
||||
write_file("temp", content);
|
||||
file f("temp", file::RDONLY);
|
||||
EXPECT_GE(f.size(), 0);
|
||||
EXPECT_EQ(content.size(), static_cast<unsigned long long>(f.size()));
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
fmt::memory_buffer message;
|
||||
fmt::internal::format_windows_error(message, ERROR_ACCESS_DENIED,
|
||||
"cannot get file size");
|
||||
fstat_sim = ERROR;
|
||||
EXPECT_THROW_MSG(f.size(), fmt::windows_error, fmt::to_string(message));
|
||||
fstat_sim = NONE;
|
||||
#else
|
||||
# else
|
||||
f.close();
|
||||
EXPECT_SYSTEM_ERROR(f.size(), EBADF, "cannot get file attributes");
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(FileTest, MaxSize) {
|
||||
write_file("test", "");
|
||||
file f("test", file::RDONLY);
|
||||
write_file("temp", "");
|
||||
file f("temp", file::RDONLY);
|
||||
fstat_sim = MAX_SIZE;
|
||||
EXPECT_GE(f.size(), 0);
|
||||
EXPECT_EQ(max_file_size(), f.size());
|
||||
@ -297,16 +302,16 @@ TEST(FileTest, WriteRetry) {
|
||||
EXPECT_RETRY(count = write_end.write("test", SIZE), write,
|
||||
"cannot write to file");
|
||||
write_end.close();
|
||||
#ifndef _WIN32
|
||||
# ifndef _WIN32
|
||||
EXPECT_EQ(static_cast<std::streamsize>(SIZE), count);
|
||||
char buffer[SIZE + 1];
|
||||
read_end.read(buffer, SIZE);
|
||||
buffer[SIZE] = '\0';
|
||||
EXPECT_STREQ("test", buffer);
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
# ifdef _WIN32
|
||||
TEST(FileTest, ConvertReadCount) {
|
||||
file read_end, write_end;
|
||||
file::pipe(read_end, write_end);
|
||||
@ -332,7 +337,7 @@ TEST(FileTest, ConvertWriteCount) {
|
||||
write_count = 0;
|
||||
EXPECT_EQ(UINT_MAX, write_nbyte);
|
||||
}
|
||||
#endif
|
||||
# endif
|
||||
|
||||
TEST(FileTest, DupNoRetry) {
|
||||
int stdout_fd = FMT_POSIX(fileno(stdout));
|
||||
@ -357,11 +362,11 @@ TEST(FileTest, Dup2NoExceptRetry) {
|
||||
error_code ec;
|
||||
dup2_count = 1;
|
||||
f1.dup2(f2.descriptor(), ec);
|
||||
#ifndef _WIN32
|
||||
# ifndef _WIN32
|
||||
EXPECT_EQ(4, dup2_count);
|
||||
#else
|
||||
# else
|
||||
EXPECT_EQ(EINTR, ec.get());
|
||||
#endif
|
||||
# endif
|
||||
dup2_count = 0;
|
||||
}
|
||||
|
||||
@ -383,15 +388,15 @@ TEST(FileTest, FdopenNoRetry) {
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, OpenRetry) {
|
||||
write_file("test", "there must be something here");
|
||||
write_file("temp", "there must be something here");
|
||||
std::unique_ptr<buffered_file> f{nullptr};
|
||||
EXPECT_RETRY(f.reset(new buffered_file("test", "r")), fopen,
|
||||
"cannot open file test");
|
||||
#ifndef _WIN32
|
||||
EXPECT_RETRY(f.reset(new buffered_file("temp", "r")), fopen,
|
||||
"cannot open file temp");
|
||||
# ifndef _WIN32
|
||||
char c = 0;
|
||||
if (fread(&c, 1, 1, f->get()) < 1)
|
||||
throw fmt::system_error(errno, "fread failed");
|
||||
#endif
|
||||
# endif
|
||||
}
|
||||
|
||||
TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||
@ -399,14 +404,15 @@ TEST(BufferedFileTest, CloseNoRetryInDtor) {
|
||||
file::pipe(read_end, write_end);
|
||||
std::unique_ptr<buffered_file> f(new buffered_file(read_end.fdopen("r")));
|
||||
int saved_fclose_count = 0;
|
||||
EXPECT_WRITE(stderr,
|
||||
{
|
||||
fclose_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_fclose_count = fclose_count;
|
||||
fclose_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_WRITE(
|
||||
stderr,
|
||||
{
|
||||
fclose_count = 1;
|
||||
f.reset(nullptr);
|
||||
saved_fclose_count = fclose_count;
|
||||
fclose_count = 0;
|
||||
},
|
||||
format_system_error(EINTR, "cannot close file") + "\n");
|
||||
EXPECT_EQ(2, saved_fclose_count);
|
||||
}
|
||||
|
||||
@ -429,34 +435,35 @@ TEST(BufferedFileTest, FilenoNoRetry) {
|
||||
EXPECT_EQ(2, fileno_count);
|
||||
fileno_count = 0;
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
struct TestMock {
|
||||
static TestMock* instance;
|
||||
} * TestMock::instance;
|
||||
struct test_mock {
|
||||
static test_mock* instance;
|
||||
} * test_mock::instance;
|
||||
|
||||
TEST(ScopedMock, Scope) {
|
||||
{
|
||||
ScopedMock<TestMock> mock;
|
||||
EXPECT_EQ(&mock, TestMock::instance);
|
||||
TestMock& copy = mock;
|
||||
ScopedMock<test_mock> mock;
|
||||
EXPECT_EQ(&mock, test_mock::instance);
|
||||
test_mock& copy = mock;
|
||||
static_cast<void>(copy);
|
||||
}
|
||||
EXPECT_EQ(nullptr, TestMock::instance);
|
||||
EXPECT_EQ(nullptr, test_mock::instance);
|
||||
}
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
|
||||
typedef fmt::Locale::type LocaleType;
|
||||
typedef fmt::locale::type locale_type;
|
||||
|
||||
struct LocaleMock {
|
||||
static LocaleMock* instance;
|
||||
MOCK_METHOD3(newlocale, LocaleType(int category_mask, const char* locale,
|
||||
LocaleType base));
|
||||
MOCK_METHOD1(freelocale, void(LocaleType locale));
|
||||
struct locale_mock {
|
||||
static locale_mock* instance;
|
||||
MOCK_METHOD3(newlocale, locale_type(int category_mask, const char* locale,
|
||||
locale_type base));
|
||||
MOCK_METHOD1(freelocale, void(locale_type locale));
|
||||
|
||||
MOCK_METHOD3(strtod_l,
|
||||
double(const char* nptr, char** endptr, LocaleType locale));
|
||||
} * LocaleMock::instance;
|
||||
double(const char* nptr, char** endptr, locale_type locale));
|
||||
} * locale_mock::instance;
|
||||
|
||||
# ifdef _MSC_VER
|
||||
# pragma warning(push)
|
||||
@ -467,15 +474,15 @@ struct LocaleMock {
|
||||
# endif
|
||||
|
||||
_locale_t _create_locale(int category, const char* locale) {
|
||||
return LocaleMock::instance->newlocale(category, locale, 0);
|
||||
return locale_mock::instance->newlocale(category, locale, 0);
|
||||
}
|
||||
|
||||
void _free_locale(_locale_t locale) {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
locale_mock::instance->freelocale(locale);
|
||||
}
|
||||
|
||||
double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
return locale_mock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
# ifdef __clang__
|
||||
# pragma clang diagnostic pop
|
||||
@ -489,11 +496,6 @@ double _strtod_l(const char* nptr, char** endptr, _locale_t locale) {
|
||||
# define FMT_LOCALE_THROW
|
||||
# endif
|
||||
|
||||
LocaleType newlocale(int category_mask, const char* locale,
|
||||
LocaleType base) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
# if defined(__APPLE__) || \
|
||||
(defined(__FreeBSD__) && __FreeBSD_version < 1200002)
|
||||
typedef int FreeLocaleResult;
|
||||
@ -501,49 +503,55 @@ typedef int FreeLocaleResult;
|
||||
typedef void FreeLocaleResult;
|
||||
# endif
|
||||
|
||||
FreeLocaleResult freelocale(LocaleType locale) FMT_LOCALE_THROW {
|
||||
LocaleMock::instance->freelocale(locale);
|
||||
FreeLocaleResult freelocale(locale_type locale) FMT_LOCALE_THROW {
|
||||
locale_mock::instance->freelocale(locale);
|
||||
return FreeLocaleResult();
|
||||
}
|
||||
|
||||
double strtod_l(const char* nptr, char** endptr,
|
||||
LocaleType locale) FMT_LOCALE_THROW {
|
||||
return LocaleMock::instance->strtod_l(nptr, endptr, locale);
|
||||
locale_type locale) FMT_LOCALE_THROW {
|
||||
return locale_mock::instance->strtod_l(nptr, endptr, locale);
|
||||
}
|
||||
|
||||
# undef FMT_LOCALE_THROW
|
||||
|
||||
TEST(LocaleTest, LocaleMock) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType locale = reinterpret_cast<LocaleType>(11);
|
||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||
newlocale(222, "foo", locale);
|
||||
# ifndef _WIN32
|
||||
locale_t test::newlocale(int category_mask, const char* locale, locale_t base) {
|
||||
return locale_mock::instance->newlocale(category_mask, locale, base);
|
||||
}
|
||||
|
||||
TEST(LocaleTest, LocaleMock) {
|
||||
ScopedMock<locale_mock> mock;
|
||||
locale_type locale = reinterpret_cast<locale_type>(11);
|
||||
EXPECT_CALL(mock, newlocale(222, StrEq("foo"), locale));
|
||||
FMT_SYSTEM(newlocale(222, "foo", locale));
|
||||
}
|
||||
# endif
|
||||
|
||||
TEST(LocaleTest, Locale) {
|
||||
# ifndef LC_NUMERIC_MASK
|
||||
enum { LC_NUMERIC_MASK = LC_NUMERIC };
|
||||
# endif
|
||||
ScopedMock<LocaleMock> mock;
|
||||
LocaleType impl = reinterpret_cast<LocaleType>(42);
|
||||
ScopedMock<locale_mock> mock;
|
||||
locale_type impl = reinterpret_cast<locale_type>(42);
|
||||
EXPECT_CALL(mock, newlocale(LC_NUMERIC_MASK, StrEq("C"), nullptr))
|
||||
.WillOnce(Return(impl));
|
||||
EXPECT_CALL(mock, freelocale(impl));
|
||||
fmt::Locale locale;
|
||||
EXPECT_EQ(impl, locale.get());
|
||||
fmt::locale loc;
|
||||
EXPECT_EQ(impl, loc.get());
|
||||
}
|
||||
|
||||
TEST(LocaleTest, Strtod) {
|
||||
ScopedMock<LocaleMock> mock;
|
||||
ScopedMock<locale_mock> mock;
|
||||
EXPECT_CALL(mock, newlocale(_, _, _))
|
||||
.WillOnce(Return(reinterpret_cast<LocaleType>(42)));
|
||||
.WillOnce(Return(reinterpret_cast<locale_type>(42)));
|
||||
EXPECT_CALL(mock, freelocale(_));
|
||||
fmt::Locale locale;
|
||||
fmt::locale loc;
|
||||
const char* str = "4.2";
|
||||
char end = 'x';
|
||||
EXPECT_CALL(mock, strtod_l(str, _, locale.get()))
|
||||
EXPECT_CALL(mock, strtod_l(str, _, loc.get()))
|
||||
.WillOnce(testing::DoAll(testing::SetArgPointee<1>(&end), Return(777)));
|
||||
EXPECT_EQ(777, locale.strtod(str));
|
||||
EXPECT_EQ(777, loc.strtod(str));
|
||||
EXPECT_EQ(&end, str);
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,11 @@
|
||||
#define FMT_POSIX_TEST_H
|
||||
|
||||
#include <errno.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#ifdef __APPLE__
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
@ -62,6 +66,10 @@ int pipe(int* pfds, unsigned psize, int textmode);
|
||||
FILE* fopen(const char* filename, const char* mode);
|
||||
int fclose(FILE* stream);
|
||||
int(fileno)(FILE* stream);
|
||||
|
||||
#if defined(FMT_LOCALE) && !defined(_WIN32)
|
||||
locale_t newlocale(int category_mask, const char* locale, locale_t base);
|
||||
#endif
|
||||
} // namespace test
|
||||
|
||||
#define FMT_SYSTEM(call) test::call
|
||||
|
@ -5,17 +5,19 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/printf.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
|
||||
#include "fmt/core.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "gtest-extra.h"
|
||||
#include "util.h"
|
||||
|
||||
using fmt::format;
|
||||
using fmt::format_error;
|
||||
using fmt::internal::max_value;
|
||||
|
||||
const unsigned BIG_NUM = INT_MAX + 1u;
|
||||
|
||||
@ -295,20 +297,19 @@ void TestLength(const char* length_spec, U value) {
|
||||
long long signed_value = 0;
|
||||
unsigned long long unsigned_value = 0;
|
||||
// Apply integer promotion to the argument.
|
||||
using std::numeric_limits;
|
||||
unsigned long long max = numeric_limits<U>::max();
|
||||
unsigned long long max = max_value<U>();
|
||||
using fmt::internal::const_check;
|
||||
if (const_check(max <= static_cast<unsigned>(numeric_limits<int>::max()))) {
|
||||
if (const_check(max <= static_cast<unsigned>(max_value<int>()))) {
|
||||
signed_value = static_cast<int>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
} else if (const_check(max <= numeric_limits<unsigned>::max())) {
|
||||
unsigned_value = static_cast<unsigned long long>(value);
|
||||
} else if (const_check(max <= max_value<unsigned>())) {
|
||||
signed_value = static_cast<unsigned>(value);
|
||||
unsigned_value = static_cast<unsigned>(value);
|
||||
unsigned_value = static_cast<unsigned long long>(value);
|
||||
}
|
||||
if (sizeof(U) <= sizeof(int) && sizeof(int) < sizeof(T)) {
|
||||
signed_value = static_cast<long long>(value);
|
||||
unsigned_value =
|
||||
static_cast<typename std::make_unsigned<unsigned>::type>(value);
|
||||
unsigned_value = static_cast<unsigned long long>(
|
||||
static_cast<typename std::make_unsigned<unsigned>::type>(value));
|
||||
} else {
|
||||
signed_value = static_cast<typename make_signed<T>::type>(value);
|
||||
unsigned_value = static_cast<typename std::make_unsigned<T>::type>(value);
|
||||
@ -332,25 +333,27 @@ void TestLength(const char* length_spec, U value) {
|
||||
}
|
||||
|
||||
template <typename T> void TestLength(const char* length_spec) {
|
||||
T min = std::numeric_limits<T>::min(), max = std::numeric_limits<T>::max();
|
||||
T min = std::numeric_limits<T>::min(), max = max_value<T>();
|
||||
TestLength<T>(length_spec, 42);
|
||||
TestLength<T>(length_spec, -42);
|
||||
TestLength<T>(length_spec, min);
|
||||
TestLength<T>(length_spec, max);
|
||||
TestLength<T>(length_spec, static_cast<long long>(min) - 1);
|
||||
unsigned long long long_long_max = std::numeric_limits<long long>::max();
|
||||
long long long_long_min = std::numeric_limits<long long>::min();
|
||||
if (static_cast<long long>(min) > long_long_min)
|
||||
TestLength<T>(length_spec, static_cast<long long>(min) - 1);
|
||||
unsigned long long long_long_max = max_value<long long>();
|
||||
if (static_cast<unsigned long long>(max) < long_long_max)
|
||||
TestLength<T>(length_spec, static_cast<long long>(max) + 1);
|
||||
TestLength<T>(length_spec, std::numeric_limits<short>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned short>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned short>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<int>::max());
|
||||
TestLength<T>(length_spec, max_value<int>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<long long>::max());
|
||||
TestLength<T>(length_spec, max_value<long long>());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min());
|
||||
TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::max());
|
||||
TestLength<T>(length_spec, max_value<unsigned long long>());
|
||||
}
|
||||
|
||||
TEST(PrintfTest, Length) {
|
||||
@ -366,7 +369,7 @@ TEST(PrintfTest, Length) {
|
||||
TestLength<intmax_t>("j");
|
||||
TestLength<std::size_t>("z");
|
||||
TestLength<std::ptrdiff_t>("t");
|
||||
long double max = std::numeric_limits<long double>::max();
|
||||
long double max = max_value<long double>();
|
||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
|
||||
EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
|
||||
}
|
||||
@ -389,7 +392,7 @@ TEST(PrintfTest, Int) {
|
||||
TEST(PrintfTest, long_long) {
|
||||
// fmt::printf allows passing long long arguments to %d without length
|
||||
// specifiers.
|
||||
long long max = std::numeric_limits<long long>::max();
|
||||
long long max = max_value<long long>();
|
||||
EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
|
||||
}
|
||||
|
||||
@ -408,6 +411,7 @@ TEST(PrintfTest, Float) {
|
||||
EXPECT_PRINTF("392.65", "%G", 392.65);
|
||||
EXPECT_PRINTF("392", "%g", 392.0);
|
||||
EXPECT_PRINTF("392", "%G", 392.0);
|
||||
EXPECT_PRINTF("4.56e-07", "%g", 0.000000456);
|
||||
safe_sprintf(buffer, "%a", -392.65);
|
||||
EXPECT_EQ(buffer, format("{:a}", -392.65));
|
||||
safe_sprintf(buffer, "%A", -392.65);
|
||||
@ -425,7 +429,7 @@ TEST(PrintfTest, Inf) {
|
||||
|
||||
TEST(PrintfTest, Char) {
|
||||
EXPECT_PRINTF("x", "%c", 'x');
|
||||
int max = std::numeric_limits<int>::max();
|
||||
int max = max_value<int>();
|
||||
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
|
||||
// EXPECT_PRINTF("x", "%lc", L'x');
|
||||
EXPECT_PRINTF(L"x", L"%c", L'x');
|
||||
@ -470,11 +474,15 @@ TEST(PrintfTest, Location) {
|
||||
// TODO: test %n
|
||||
}
|
||||
|
||||
enum E { A = 42 };
|
||||
enum test_enum { answer = 42 };
|
||||
|
||||
TEST(PrintfTest, Enum) { EXPECT_PRINTF("42", "%d", A); }
|
||||
TEST(PrintfTest, Enum) {
|
||||
EXPECT_PRINTF("42", "%d", answer);
|
||||
volatile test_enum volatile_enum = answer;
|
||||
EXPECT_PRINTF("42", "%d", volatile_enum);
|
||||
}
|
||||
|
||||
#if FMT_USE_FILE_DESCRIPTORS
|
||||
#if FMT_USE_FCNTL
|
||||
TEST(PrintfTest, Examples) {
|
||||
const char* weekday = "Thursday";
|
||||
const char* month = "August";
|
||||
@ -494,7 +502,9 @@ TEST(PrintfTest, PrintfError) {
|
||||
TEST(PrintfTest, WideString) { EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc")); }
|
||||
|
||||
TEST(PrintfTest, PrintfCustom) {
|
||||
EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
|
||||
// The test is disabled for now because it requires decoupling
|
||||
// fallback_formatter::format from format_context.
|
||||
//EXPECT_EQ("abc", test_sprintf("%s", TestString("abc")));
|
||||
}
|
||||
|
||||
TEST(PrintfTest, OStream) {
|
||||
@ -561,8 +571,7 @@ TEST(PrintfTest, VSPrintfMakeWArgsExample) {
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef fmt::printf_arg_formatter<fmt::internal::buffer_range<char>>
|
||||
formatter_t;
|
||||
typedef fmt::printf_arg_formatter<fmt::buffer_range<char>> formatter_t;
|
||||
typedef fmt::basic_printf_context<formatter_t::iterator, char> context_t;
|
||||
|
||||
// A custom printf argument formatter that doesn't print `-` for floating-point
|
||||
|
@ -9,13 +9,14 @@
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
/// Check if 'if constexpr' is supported.
|
||||
#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 "fmt/ranges.h"
|
||||
# include "gtest.h"
|
||||
|
||||
# include <array>
|
||||
# include <map>
|
||||
# include <string>
|
||||
@ -44,9 +45,35 @@ TEST(RangesTest, FormatPair) {
|
||||
}
|
||||
|
||||
TEST(RangesTest, FormatTuple) {
|
||||
std::tuple<int64_t, float, std::string, char> tu1{42, 1.5f, "this is tuple",
|
||||
'i'};
|
||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1));
|
||||
std::tuple<int64_t, float, std::string, char> t{42, 1.5f, "this is tuple",
|
||||
'i'};
|
||||
EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", t));
|
||||
EXPECT_EQ("()", fmt::format("{}", std::tuple<>()));
|
||||
}
|
||||
|
||||
TEST(RangesTest, JoinTuple) {
|
||||
// Value tuple args
|
||||
std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
|
||||
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) {
|
||||
EXPECT_EQ("1, 2, 3", fmt::format("{}", fmt::join({1, 2, 3}, ", ")));
|
||||
EXPECT_EQ("fmt rocks !",
|
||||
fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")));
|
||||
}
|
||||
|
||||
struct my_struct {
|
||||
@ -100,3 +127,16 @@ TEST(RangesTest, PathLike) {
|
||||
|
||||
#endif // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
|
||||
// 201402L && _MSC_VER >= 1910)
|
||||
|
||||
#ifdef FMT_USE_STRING_VIEW
|
||||
struct string_like {
|
||||
const char* begin();
|
||||
const char* end();
|
||||
explicit operator fmt::string_view() const { return "foo"; }
|
||||
explicit operator std::string_view() const { return "foo"; }
|
||||
};
|
||||
|
||||
TEST(RangesTest, FormatStringLike) {
|
||||
EXPECT_EQ("foo", fmt::format("{}", string_like()));
|
||||
}
|
||||
#endif // FMT_USE_STRING_VIEW
|
||||
|
18
test/scan.h
18
test/scan.h
@ -6,6 +6,8 @@
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
|
||||
#include "fmt/format.h"
|
||||
|
||||
@ -135,21 +137,20 @@ struct scan_handler : error_handler {
|
||||
char c = *it++;
|
||||
if (c < '0' || c > '9') on_error("invalid input");
|
||||
// TODO: check overflow
|
||||
value = value * 10 + (c - '0');
|
||||
value = value * 10 + static_cast<unsigned>(c - '0');
|
||||
}
|
||||
scan_ctx_.advance_to(it);
|
||||
return value;
|
||||
}
|
||||
|
||||
template <typename T = int> T read_int() {
|
||||
T value = 0;
|
||||
auto it = scan_ctx_.begin(), end = scan_ctx_.end();
|
||||
bool negative = it != end && *it == '-';
|
||||
if (negative) ++it;
|
||||
scan_ctx_.advance_to(it);
|
||||
value = read_uint<typename std::make_unsigned<T>::type>();
|
||||
if (negative) value = -value;
|
||||
return value;
|
||||
const auto value = read_uint<typename std::make_unsigned<T>::type>();
|
||||
if (negative) return -static_cast<T>(value);
|
||||
return static_cast<T>(value);
|
||||
}
|
||||
|
||||
public:
|
||||
@ -159,7 +160,7 @@ struct scan_handler : error_handler {
|
||||
const char* pos() const { return scan_ctx_.begin(); }
|
||||
|
||||
void on_text(const char* begin, const char* end) {
|
||||
auto size = end - begin;
|
||||
auto size = to_unsigned(end - begin);
|
||||
auto it = scan_ctx_.begin();
|
||||
if (it + size > scan_ctx_.end() ||
|
||||
!std::equal(begin, end, make_checked(it, size))) {
|
||||
@ -197,11 +198,12 @@ struct scan_handler : error_handler {
|
||||
case scan_type::string_view_type: {
|
||||
auto s = it;
|
||||
while (it != end && *it != ' ') ++it;
|
||||
*arg_.string_view = fmt::string_view(s, it - s);
|
||||
*arg_.string_view = fmt::string_view(s, to_unsigned(it - s));
|
||||
scan_ctx_.advance_to(it);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case scan_type::none_type:
|
||||
case scan_type::custom_type:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
@ -29,19 +29,15 @@ TEST(StdFormatTest, Alignment) {
|
||||
string s4 = format("{:*^6}", 'x'); // s4 == "**x***"
|
||||
// Error: '=' with charT and no integer presentation type
|
||||
EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error);
|
||||
string s6 = format("{:6d}", c); // s6 == " 120"
|
||||
string s7 = format("{:=+06d}", c); // s7 == "+00120"
|
||||
string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a"
|
||||
string s9 = format("{:6}", true); // s9 == "true "
|
||||
string s6 = format("{:6d}", c); // s6 == " 120"
|
||||
string s7 = format("{:6}", true); // s9 == "true "
|
||||
EXPECT_EQ(s0, " 42");
|
||||
EXPECT_EQ(s1, "x ");
|
||||
EXPECT_EQ(s2, "x*****");
|
||||
EXPECT_EQ(s3, "*****x");
|
||||
EXPECT_EQ(s4, "**x***");
|
||||
EXPECT_EQ(s6, " 120");
|
||||
EXPECT_EQ(s7, "+00120");
|
||||
EXPECT_EQ(s8, "0x000a");
|
||||
EXPECT_EQ(s9, "true ");
|
||||
EXPECT_EQ(s7, "true ");
|
||||
}
|
||||
|
||||
TEST(StdFormatTest, Float) {
|
||||
@ -65,11 +61,11 @@ TEST(StdFormatTest, Int) {
|
||||
string s0 = format("{}", 42); // s0 == "42"
|
||||
string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a"
|
||||
string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A"
|
||||
string s3 = format("{:n}", 1234); // s3 == "1,234" (depends on the locale)
|
||||
string s3 = format("{:n}", 1234); // s3 == "1234" (depends on the locale)
|
||||
EXPECT_EQ(s0, "42");
|
||||
EXPECT_EQ(s1, "101010 42 52 2a");
|
||||
EXPECT_EQ(s2, "0x2a 0X2A");
|
||||
EXPECT_EQ(s3, "1,234");
|
||||
EXPECT_EQ(s3, "1234");
|
||||
}
|
||||
|
||||
#include <format>
|
||||
|
@ -14,8 +14,13 @@
|
||||
class assertion_failure : public std::logic_error {
|
||||
public:
|
||||
explicit assertion_failure(const char* message) : std::logic_error(message) {}
|
||||
|
||||
private:
|
||||
virtual void avoid_weak_vtable();
|
||||
};
|
||||
|
||||
void assertion_failure::avoid_weak_vtable() {}
|
||||
|
||||
#define FMT_ASSERT(condition, message) \
|
||||
if (!(condition)) throw assertion_failure(message);
|
||||
|
||||
|
@ -33,11 +33,17 @@ std::string get_system_error(int error_code) {
|
||||
const char* const FILE_CONTENT = "Don't panic!";
|
||||
|
||||
fmt::buffered_file open_buffered_file(FILE** fp) {
|
||||
#if FMT_USE_FCNTL
|
||||
fmt::file read_end, write_end;
|
||||
fmt::file::pipe(read_end, write_end);
|
||||
write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT));
|
||||
write_end.close();
|
||||
fmt::buffered_file f = read_end.fdopen("r");
|
||||
if (fp) *fp = f.get();
|
||||
#else
|
||||
fmt::buffered_file f("test-file", "w");
|
||||
fputs(FILE_CONTENT, f.get());
|
||||
if (fp) *fp = f.get();
|
||||
#endif
|
||||
return f;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "fmt/posix.h"
|
||||
#include "fmt/os.h"
|
||||
|
||||
enum { BUFFER_SIZE = 256 };
|
||||
|
||||
|
Reference in New Issue
Block a user