Compare commits

...

37 Commits

Author SHA1 Message Date
gabime
486b55554f Version 1.16.0 2025-10-11 15:53:05 +03:00
gabime
1bea38edcc clang-format 2025-10-11 15:50:16 +03:00
gabime
4418909af2 Bump fmt to 12.0.0 2025-10-11 15:18:15 +03:00
Angelio Mason
f1d748e5e3 Remove the fileapi.h include in os-inl.h (#3444)
* Replaced fileapi.h include with windows.h, as instructed in https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
Otherwise this causes compilation error on older sdks.

* Removed the fileapi.h include entirely, since windows.h is already included before

---------

Co-authored-by: MasonAngelio <MasonAngelio>
2025-08-07 23:47:11 +03:00
Vitaly
3edc8036db Run tests in the order they are declared in the source file. (#3451)
Fixes an issue with running tests in random order in Catch2 3.9.0+.
2025-08-07 23:38:29 +03:00
Mihir Patel
9ecdf5c8a1 Added timeout for TCP calls such as connect, send, recv (#3432)
* Now lets test on windows

* I guess testing on windows passes.

* Update tcp_client-windows.h

Added default value to argument

* Final edit

* Update tcp_client-windows.h

Changed improper misplaced includes.
2025-07-17 23:47:35 +03:00
Gabi Melman
737347d2df Update linux.yml 2025-07-16 09:55:57 +03:00
Alexander
4f2b3d52f9 Update README.md (#3437)
* Update README.md

add example showcasing 2 loggers and `spdlog::set_level()` 
which set level not only to default logger, but to all registed loggers

* Update README.md

* simplify

* simplify
2025-07-16 08:59:51 +03:00
Joshua Chapman
4397dac510 chore(cmake): add option to override CMAKE_DEBUG_POSTFIX (#3433)
This will make it possible to use the pkg-config with CMake debug build.
2025-07-08 22:52:42 +03:00
Gabi Melman
6fd67ce169 Update windows.yml
remove msvc 2019 build
2025-07-06 10:08:55 +03:00
Gabi Melman
4619e18a16 Update windows.yml 2025-07-06 09:53:42 +03:00
Gabi Melman
a6215527f4 Fix ringbuffer tests for newline (#3436) 2025-07-06 08:38:48 +03:00
VZ
287333ee00 Remove unnecessary and inconsistent "final" from color sinks (#3430)
The use of "final" differed between ansicolor_sink and wincolor_sink,
resulting in the code inheriting from std{err,out}_color_sink classes,
which are defined as one or the other on different platforms, being able
to override most of the functions under non-Windows platforms, but not
under Windows.

This seems gratuitously inconsistent, so just remove all "final"
keywords from both classes, especially because there doesn't seem any
good reason to use it and the other sink classes don't use it (with the
exception of base_sink, which is special).

This also incidentally fixes using "final override" in most places but
"override final" in wincolor_sink.h.

Fixes #3429.
2025-06-30 07:39:32 +03:00
电线杆
ad725d34cc Use std::getenv #3414 (#3415) 2025-06-08 23:16:34 +03:00
Gabi Melman
e655dbb685 Fix issue #3408
Remove including core.h or base.h
2025-06-07 13:44:09 +03:00
gabime
b18a234ed6 Fix coverity ci 2025-05-12 17:44:17 +03:00
Gabi Melman
5d89b5b91c Update jetbrains logo (#3401)
* Update jetbrains logo
2025-05-12 16:04:04 +03:00
Gabi Melman
37ff466454 Add coverity scan to CI and fix warnings (#3400)
* Move callback function in thread_pool ctor

* Added const qualifiers to logger.h

* Remove unused includes from file_helper-inl.h

* Fix comments and remove unused include from helpers-inl.h

* Fix typo in comment for set_default_logger method.

* Use `std::move` for `old_logger` in `set_default_logger`.

* Use std::move in example

* Wrap `main` content in try block for exception safety.

* Added coverity to ci
2025-05-12 15:36:07 +03:00
Gabi Melman
677a2d93e6 Update test_stopwatch.cpp 2025-05-09 13:22:39 +03:00
Gabi Melman
6fa36017cf Version 1.15.3 2025-05-09 08:55:57 +03:00
Gabi Melman
c73b8cc400 Update comment 2025-05-09 02:50:11 +03:00
Gabi Melman
7ca6a4fb27 Update commemt 2025-05-09 02:49:36 +03:00
Gabi Melman
070e1c9747 Update comment 2025-05-09 02:46:10 +03:00
Gabi Melman
0d31acae28 Fmt 11.2.0 (#3399)
* Bump fmt to 11.2.0
2025-05-08 20:13:03 +03:00
Gabi Melman
943fcbd761 Register replace logger (#3398)
* Add register_or_replace(logger) to atomically replace logger in registry
* fix some  comments
2025-05-08 15:42:54 +03:00
Gabi Melman
7e022c4300 Feature 3379 (#3397)
* Fix #3379

* clang format
2025-05-08 14:02:00 +03:00
Hinageshi
548b264254 Fix warning C4530 (#3393)
* Fix warning C4530

* Rename FMT_EXCEPTIONS to FMT_USE_EXCEPTIONS
2025-04-23 19:46:25 +03:00
Tihran Katolikian
847db3375f dup_filter_sink: remove notification_level argument; use last message log level for notification instead (#3390) 2025-04-18 20:45:56 +03:00
Dmitry Kozlovtsev
bb8694b50f Fix links for #3380 (#3381) 2025-04-14 19:22:47 +03:00
Christoph Gringmuth
cec28bf839 Fix links to local reference. (#3378)
Enables local navigation in IDE and removes links to branch.
2025-04-10 17:58:19 +03:00
Gabi Melman
bd0609d7a0 Update README.md 2025-04-10 17:06:12 +03:00
Christoph Gringmuth
1f4959c832 Fix link to wiki. (#3377) 2025-04-10 17:04:23 +03:00
gabime
48bcf39a66 Version 1.15.2 2025-03-29 14:01:07 +03:00
Gabi Melman
9c58257480 Fix zformatter on Apple and POSIX.1-2024 conforming platform (#3366)
* Add test case for #3351 (wrong GMT offset in SunOS/Solaris fallback)

* Fix #3352 (Missing test for Apple / POSIX.1-2024 chooses buggy workaround)

Apple platforms have had the tm_gmtoff-field at least since Mac OS X 10.0,
as are POSIX.1-2024 conforming systems, which are also required to support
it.

This has the unfortunate effect to use the SunOS/Solaris fallback, which
doesn't compute the correct value if the passed value of tm isn't the
current system time, i.e. localtime(::time()) (#3351).

* Fixed GMT offset test

---------

Co-authored-by: toh <toh@ableton.com>
2025-03-29 13:44:11 +03:00
gabime
faa0a7a9c5 Bump fmt to version 11.1.4 2025-03-17 16:56:21 +02:00
Gabi Melman
10320184df Fixed issue #3360 (#3361) 2025-03-17 15:46:31 +02:00
Александр
3335c380a0 Update README.md (#3338)
How to install this package in ALT Linux.
2025-02-11 19:10:50 +02:00
64 changed files with 2262 additions and 1793 deletions

52
.github/workflows/coverity_scan.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: coverity-linux
on: [push, pull_request]
permissions:
contents: read
jobs:
coverity_scan:
runs-on: ubuntu-latest
name: Coverity Scan
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y curl build-essential cmake pkg-config libsystemd-dev
- name: Download Coverity Tool
run: |
curl -s -L --output coverity_tool.tgz "https://scan.coverity.com/download/linux64?token=${{ secrets.COVERITY_TOKEN }}&project=gabime%2Fspdlog"
mkdir coverity_tool
tar -C coverity_tool --strip-components=1 -xf coverity_tool.tgz
echo "$PWD/coverity_tool/bin" >> $GITHUB_PATH
- name: Build with Coverity
run: |
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17
cd ..
cov-build --dir cov-int make -C build -j4
- name: Submit results to Coverity
run: |
tar czf cov-int.tgz cov-int
response=$(curl --silent --show-error --fail \
--form email="${{ secrets.EMAIL }}" \
--form token="${{ secrets.COVERITY_TOKEN }}" \
--form file=@cov-int.tgz \
--form version="GitHub PR #${{ github.event.pull_request.number }}" \
--form description="CI run for PR" \
https://scan.coverity.com/builds?project=gabime%2Fspdlog)
echo "$response"
if echo "$response" | grep -qi "Build successfully submitted"; then
echo "Coverity upload succeeded"
else
echo "Coverity upload failed or was rejected"
exit 1
fi

View File

@@ -18,9 +18,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
config: config:
- { compiler: gcc, version: 7, build_type: Release, cppstd: 11 } - { compiler: gcc, version: 9, build_type: Release, cppstd: 11 }
- { compiler: gcc, version: 9, build_type: Release, cppstd: 17 } - { compiler: gcc, version: 11, build_type: Debug, cppstd: 17 }
- { compiler: gcc, version: 11, build_type: Debug, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Release, cppstd: 20 } - { compiler: gcc, version: 12, build_type: Release, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON } - { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON }
- { compiler: clang, version: 12, build_type: Debug, cppstd: 17 } - { compiler: clang, version: 12, build_type: Debug, cppstd: 17 }

View File

@@ -75,74 +75,5 @@ jobs:
run: | run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe
# -----------------------------------------------------------------------
# MSVC 2019 build matrix
# -----------------------------------------------------------------------
build_2019:
runs-on: windows-2019
strategy:
fail-fast: true
matrix:
config:
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 17
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 14
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: CMake ${{ matrix.config.GENERATOR }} CXX=${{matrix.config.CXX_STANDARD}} WCHAR=${{matrix.config.WCHAR_FILES}} STD_FORMAT=${{matrix.config.USE_STD_FORMAT}}
shell: pwsh
run: |
mkdir build
cd build
cmake -G "${{ matrix.config.GENERATOR }}" -A x64 `
-D CMAKE_BUILD_TYPE=${{ matrix.config.BUILD_TYPE }} `
-D BUILD_SHARED_LIBS=${{ matrix.config.BUILD_SHARED }} `
-D SPDLOG_WCHAR_SUPPORT=${{ matrix.config.WCHAR }} `
-D SPDLOG_WCHAR_FILENAMES=${{ matrix.config.WCHAR_FILES }} `
-D SPDLOG_BUILD_EXAMPLE=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_EXAMPLE_HO=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_TESTS=ON `
-D SPDLOG_BUILD_TESTS_HO=OFF `
-D SPDLOG_BUILD_WARNINGS=${{ matrix.config.FATAL_ERRORS }} `
-D SPDLOG_USE_STD_FORMAT=${{ matrix.config.USE_STD_FORMAT }} `
-D CMAKE_CXX_STANDARD=${{ matrix.config.CXX_STANDARD }} ..
- name: Build
shell: pwsh
run: |
cd build
cmake --build . --parallel --config ${{ matrix.config.BUILD_TYPE }}
- name: Run Tests
shell: pwsh
env:
PATH: ${{ env.PATH }};${{ github.workspace }}\build\_deps\catch2-build\src\${{ matrix.config.BUILD_TYPE }};${{ github.workspace }}\build\${{ matrix.config.BUILD_TYPE }}
run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe

View File

@@ -18,39 +18,38 @@ include(GNUInstallDirs)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Set default build to release # Set default build to release
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Compiler config # Compiler config
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_USE_STD_FORMAT) if(SPDLOG_USE_STD_FORMAT)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
elseif (NOT CMAKE_CXX_STANDARD) elseif(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif () endif()
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
if (CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW") if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW")
set(CMAKE_CXX_EXTENSIONS ON) set(CMAKE_CXX_EXTENSIONS ON)
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog # Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Check if spdlog is being used directly or via add_subdirectory, but allow overriding # Check if spdlog is being used directly or via add_subdirectory, but allow overriding
if (NOT DEFINED SPDLOG_MASTER_PROJECT) if(NOT DEFINED SPDLOG_MASTER_PROJECT)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(SPDLOG_MASTER_PROJECT ON) set(SPDLOG_MASTER_PROJECT ON)
else () else()
set(SPDLOG_MASTER_PROJECT OFF) set(SPDLOG_MASTER_PROJECT OFF)
endif () endif()
endif () endif()
option(SPDLOG_BUILD_ALL "Build all artifacts" OFF) option(SPDLOG_BUILD_ALL "Build all artifacts" OFF)
@@ -63,6 +62,9 @@ option(SPDLOG_ENABLE_PCH "Build static or shared library using precompiled heade
# build position independent code # build position independent code
option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF) option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF)
# debug build postfix
set(SPDLOG_DEBUG_POSTFIX "d" CACHE STRING "Filename postfix for libraries in debug builds")
# example options # example options
option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT}) option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF) option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF)
@@ -77,9 +79,9 @@ option(SPDLOG_BUILD_BENCH "Build benchmarks (Requires https://github.com/google/
# sanitizer options # sanitizer options
option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF) option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
option(SPDLOG_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF) option(SPDLOG_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF)
if (SPDLOG_SANITIZE_ADDRESS AND SPDLOG_SANITIZE_THREAD) if(SPDLOG_SANITIZE_ADDRESS AND SPDLOG_SANITIZE_THREAD)
message(FATAL_ERROR "SPDLOG_SANITIZE_ADDRESS and SPDLOG_SANITIZE_THREAD are mutually exclusive") message(FATAL_ERROR "SPDLOG_SANITIZE_ADDRESS and SPDLOG_SANITIZE_THREAD are mutually exclusive")
endif () endif()
# warning options # warning options
option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF) option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF)
@@ -92,61 +94,61 @@ option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)
option(SPDLOG_FMT_EXTERNAL_HO "Use external fmt header-only library instead of bundled" OFF) option(SPDLOG_FMT_EXTERNAL_HO "Use external fmt header-only library instead of bundled" OFF)
option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF) option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF)
if (SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO) if(SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO)
message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive") message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif () endif()
if (SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL_HO) if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL_HO)
message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive") message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif () endif()
if (SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL) if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL)
message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive") message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive")
endif () endif()
# misc tweakme options # misc tweakme options
if (WIN32) if(WIN32)
option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF) option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF)
option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF) option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF)
option(SPDLOG_WCHAR_CONSOLE "Support wchar output to console" OFF) option(SPDLOG_WCHAR_CONSOLE "Support wchar output to console" OFF)
else () else()
set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE)
set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE)
set(SPDLOG_WCHAR_CONSOLE OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_CONSOLE OFF CACHE BOOL "non supported option" FORCE)
endif () endif()
if (MSVC) if(MSVC)
option(SPDLOG_MSVC_UTF8 "Enable/disable msvc /utf-8 flag required by fmt lib" ON) option(SPDLOG_MSVC_UTF8 "Enable/disable msvc /utf-8 flag required by fmt lib" ON)
endif () endif()
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF) option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
else () else()
set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE)
endif () endif()
option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF) option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF)
option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF) option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF)
option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF) option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF)
option( option(
SPDLOG_NO_ATOMIC_LEVELS SPDLOG_NO_ATOMIC_LEVELS
"prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently" "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently"
OFF) OFF)
option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF) option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF)
option(SPDLOG_FWRITE_UNLOCKED "Use the unlocked variant of fwrite. Leave this on unless your libc doesn't have it" ON) option(SPDLOG_FWRITE_UNLOCKED "Use the unlocked variant of fwrite. Leave this on unless your libc doesn't have it" ON)
# clang-tidy # clang-tidy
option(SPDLOG_TIDY "run clang-tidy" OFF) option(SPDLOG_TIDY "run clang-tidy" OFF)
if (SPDLOG_TIDY) if(SPDLOG_TIDY)
set(CMAKE_CXX_CLANG_TIDY "clang-tidy") set(CMAKE_CXX_CLANG_TIDY "clang-tidy")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
message(STATUS "Enabled clang-tidy") message(STATUS "Enabled clang-tidy")
endif () endif()
if (SPDLOG_BUILD_PIC) if(SPDLOG_BUILD_PIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif () endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
@@ -155,56 +157,56 @@ message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
set(SPDLOG_SRCS src/spdlog.cpp src/stdout_sinks.cpp src/color_sinks.cpp src/file_sinks.cpp src/async.cpp src/cfg.cpp) set(SPDLOG_SRCS src/spdlog.cpp src/stdout_sinks.cpp src/color_sinks.cpp src/file_sinks.cpp src/async.cpp src/cfg.cpp)
if (NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
list(APPEND SPDLOG_SRCS src/bundled_fmtlib_format.cpp) list(APPEND SPDLOG_SRCS src/bundled_fmtlib_format.cpp)
endif () endif()
if (SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS) if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS)
if (WIN32) if(WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
endif () endif()
add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB) target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251 target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>) /wd4275>)
endif () endif()
if (NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
target_compile_definitions(spdlog PRIVATE FMT_LIB_EXPORT PUBLIC FMT_SHARED) target_compile_definitions(spdlog PRIVATE FMT_LIB_EXPORT PUBLIC FMT_SHARED)
endif () endif()
else () else()
add_library(spdlog STATIC ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) add_library(spdlog STATIC ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
endif () endif()
add_library(spdlog::spdlog ALIAS spdlog) add_library(spdlog::spdlog ALIAS spdlog)
set(SPDLOG_INCLUDES_LEVEL "") set(SPDLOG_INCLUDES_LEVEL "")
if (SPDLOG_SYSTEM_INCLUDES) if(SPDLOG_SYSTEM_INCLUDES)
set(SPDLOG_INCLUDES_LEVEL "SYSTEM") set(SPDLOG_INCLUDES_LEVEL "SYSTEM")
endif () endif()
target_compile_definitions(spdlog PUBLIC SPDLOG_COMPILED_LIB) target_compile_definitions(spdlog PUBLIC SPDLOG_COMPILED_LIB)
target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>") "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(spdlog PUBLIC Threads::Threads) target_link_libraries(spdlog PUBLIC Threads::Threads)
spdlog_enable_warnings(spdlog) spdlog_enable_warnings(spdlog)
set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION
${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR}) ${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR})
set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX d) set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX ${SPDLOG_DEBUG_POSTFIX})
if (COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH) if(COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY)
target_precompile_headers(spdlog PRIVATE ${PROJECT_BINARY_DIR}/spdlog_pch.h) target_precompile_headers(spdlog PRIVATE ${PROJECT_BINARY_DIR}/spdlog_pch.h)
endif () endif()
# sanitizer support # sanitizer support
if (SPDLOG_SANITIZE_ADDRESS) if(SPDLOG_SANITIZE_ADDRESS)
spdlog_enable_addr_sanitizer(spdlog) spdlog_enable_addr_sanitizer(spdlog)
elseif (SPDLOG_SANITIZE_THREAD) elseif(SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(spdlog) spdlog_enable_thread_sanitizer(spdlog)
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Header only version # Header only version
@@ -213,132 +215,134 @@ add_library(spdlog_header_only INTERFACE)
add_library(spdlog::spdlog_header_only ALIAS spdlog_header_only) add_library(spdlog::spdlog_header_only ALIAS spdlog_header_only)
target_include_directories( target_include_directories(
spdlog_header_only ${SPDLOG_INCLUDES_LEVEL} INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" spdlog_header_only ${SPDLOG_INCLUDES_LEVEL} INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>") "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(spdlog_header_only INTERFACE Threads::Threads) target_link_libraries(spdlog_header_only INTERFACE Threads::Threads)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Use fmt package if using external fmt # Use fmt package if using external fmt
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO) if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
if (NOT TARGET fmt::fmt) if(NOT TARGET fmt::fmt)
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
endif () endif()
target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL) target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL) target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL)
# use external fmt-header-only # use external fmt-header-only
if (SPDLOG_FMT_EXTERNAL_HO) if(SPDLOG_FMT_EXTERNAL_HO)
target_link_libraries(spdlog PUBLIC fmt::fmt-header-only) target_link_libraries(spdlog PUBLIC fmt::fmt-header-only)
target_link_libraries(spdlog_header_only INTERFACE fmt::fmt-header-only) target_link_libraries(spdlog_header_only INTERFACE fmt::fmt-header-only)
else () # use external compile fmt else() # use external compile fmt
target_link_libraries(spdlog PUBLIC fmt::fmt) target_link_libraries(spdlog PUBLIC fmt::fmt)
target_link_libraries(spdlog_header_only INTERFACE fmt::fmt) target_link_libraries(spdlog_header_only INTERFACE fmt::fmt)
endif () endif()
set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Check if fwrite_unlocked/_fwrite_nolock is available # Check if fwrite_unlocked/_fwrite_nolock is available
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_FWRITE_UNLOCKED) if(SPDLOG_FWRITE_UNLOCKED)
include(CheckSymbolExists) include(CheckSymbolExists)
if (WIN32) if(WIN32)
check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED) check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED)
else () else()
check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED) check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED)
endif () endif()
if (HAVE_FWRITE_UNLOCKED) if(HAVE_FWRITE_UNLOCKED)
target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED) target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED) target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED)
endif () endif()
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Add required libraries for Android CMake build # Add required libraries for Android CMake build
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (ANDROID) if(ANDROID)
target_link_libraries(spdlog PUBLIC log) target_link_libraries(spdlog PUBLIC log)
target_link_libraries(spdlog_header_only INTERFACE log) target_link_libraries(spdlog_header_only INTERFACE log)
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Misc definitions according to tweak options # Misc definitions according to tweak options
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT}) set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT})
set(SPDLOG_UTF8_TO_WCHAR_CONSOLE ${SPDLOG_WCHAR_CONSOLE}) set(SPDLOG_UTF8_TO_WCHAR_CONSOLE ${SPDLOG_WCHAR_CONSOLE})
foreach ( foreach(
SPDLOG_OPTION SPDLOG_OPTION
SPDLOG_WCHAR_TO_UTF8_SUPPORT SPDLOG_WCHAR_TO_UTF8_SUPPORT
SPDLOG_UTF8_TO_WCHAR_CONSOLE SPDLOG_UTF8_TO_WCHAR_CONSOLE
SPDLOG_WCHAR_FILENAMES SPDLOG_WCHAR_FILENAMES
SPDLOG_NO_EXCEPTIONS SPDLOG_NO_EXCEPTIONS
SPDLOG_CLOCK_COARSE SPDLOG_CLOCK_COARSE
SPDLOG_PREVENT_CHILD_FD SPDLOG_PREVENT_CHILD_FD
SPDLOG_NO_THREAD_ID SPDLOG_NO_THREAD_ID
SPDLOG_NO_TLS SPDLOG_NO_TLS
SPDLOG_NO_ATOMIC_LEVELS SPDLOG_NO_ATOMIC_LEVELS
SPDLOG_DISABLE_DEFAULT_LOGGER SPDLOG_DISABLE_DEFAULT_LOGGER
SPDLOG_USE_STD_FORMAT) SPDLOG_USE_STD_FORMAT)
if (${SPDLOG_OPTION}) if(${SPDLOG_OPTION})
target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION}) target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION})
target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION}) target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION})
endif () endif()
endforeach () endforeach()
if (MSVC) if(MSVC)
target_compile_options(spdlog PRIVATE "/Zc:__cplusplus") target_compile_options(spdlog PRIVATE "/Zc:__cplusplus")
target_compile_options(spdlog_header_only INTERFACE "/Zc:__cplusplus") target_compile_options(spdlog_header_only INTERFACE "/Zc:__cplusplus")
if (SPDLOG_MSVC_UTF8) if(SPDLOG_MSVC_UTF8)
# fmtlib requires the /utf-8 flag when building with msvc. # fmtlib requires the /utf-8 flag when building with msvc. see https://github.com/fmtlib/fmt/pull/4159 on the
# see https://github.com/fmtlib/fmt/pull/4159 on the purpose of the additional # purpose of the additional
# "$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>" # "$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>"
target_compile_options(spdlog PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>) target_compile_options(spdlog PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(spdlog_header_only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>) target_compile_options(spdlog_header_only
endif () INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
endif () endif()
endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# If exceptions are disabled, disable them in the bundled fmt as well # If exceptions are disabled, disable them in the bundled fmt as well
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_NO_EXCEPTIONS) if(SPDLOG_NO_EXCEPTIONS)
if (NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
target_compile_definitions(spdlog PUBLIC FMT_EXCEPTIONS=0) target_compile_definitions(spdlog PUBLIC FMT_USE_EXCEPTIONS=0)
endif () endif()
if (NOT MSVC) if(NOT MSVC)
target_compile_options(spdlog PRIVATE -fno-exceptions) target_compile_options(spdlog PRIVATE -fno-exceptions)
else () else()
target_compile_options(spdlog PRIVATE /EHs-c-) target_compile_options(spdlog PRIVATE /EHs-c-)
endif () target_compile_definitions(spdlog PRIVATE _HAS_EXCEPTIONS=0)
endif () endif()
endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Build binaries # Build binaries
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_EXAMPLE_HO OR SPDLOG_BUILD_ALL) if(SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_EXAMPLE_HO OR SPDLOG_BUILD_ALL)
message(STATUS "Generating example(s)") message(STATUS "Generating example(s)")
add_subdirectory(example) add_subdirectory(example)
spdlog_enable_warnings(example) spdlog_enable_warnings(example)
if (SPDLOG_BUILD_EXAMPLE_HO) if(SPDLOG_BUILD_EXAMPLE_HO)
spdlog_enable_warnings(example_header_only) spdlog_enable_warnings(example_header_only)
endif () endif()
endif () endif()
if (SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL) if(SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL)
message(STATUS "Generating tests") message(STATUS "Generating tests")
enable_testing() enable_testing()
add_subdirectory(tests) add_subdirectory(tests)
endif () endif()
if (SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL) if(SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL)
message(STATUS "Generating benchmarks") message(STATUS "Generating benchmarks")
add_subdirectory(bench) add_subdirectory(bench)
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Install # Install
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (SPDLOG_INSTALL) if(SPDLOG_INSTALL)
message(STATUS "Generating install") message(STATUS "Generating install")
set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in") set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in")
set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake") set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake")
@@ -353,30 +357,30 @@ if (SPDLOG_INSTALL)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "fmt/bundled" EXCLUDE) install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "fmt/bundled" EXCLUDE)
install( install(
TARGETS spdlog spdlog_header_only TARGETS spdlog spdlog_header_only
EXPORT spdlog EXPORT spdlog
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
if (NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
install(DIRECTORY include/${PROJECT_NAME}/fmt/bundled/ install(DIRECTORY include/${PROJECT_NAME}/fmt/bundled/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/") DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/")
endif () endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Install pkg-config file # Install pkg-config file
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else () else()
set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif () endif()
if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else () else()
set(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") set(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
endif () endif()
get_target_property(PKG_CONFIG_DEFINES spdlog INTERFACE_COMPILE_DEFINITIONS) get_target_property(PKG_CONFIG_DEFINES spdlog INTERFACE_COMPILE_DEFINITIONS)
string(REPLACE ";" " -D" PKG_CONFIG_DEFINES "${PKG_CONFIG_DEFINES}") string(REPLACE ";" " -D" PKG_CONFIG_DEFINES "${PKG_CONFIG_DEFINES}")
string(CONCAT PKG_CONFIG_DEFINES "-D" "${PKG_CONFIG_DEFINES}") string(CONCAT PKG_CONFIG_DEFINES "-D" "${PKG_CONFIG_DEFINES}")
@@ -387,7 +391,7 @@ if (SPDLOG_INSTALL)
# Install CMake config files # Install CMake config files
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
export(TARGETS spdlog spdlog_header_only NAMESPACE spdlog:: export(TARGETS spdlog spdlog_header_only NAMESPACE spdlog::
FILE "${CMAKE_CURRENT_BINARY_DIR}/${config_targets_file}") FILE "${CMAKE_CURRENT_BINARY_DIR}/${config_targets_file}")
install(EXPORT spdlog DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file}) install(EXPORT spdlog DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file})
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
@@ -400,4 +404,4 @@ if (SPDLOG_INSTALL)
# Support creation of installable packages # Support creation of installable packages
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
include(cmake/spdlogCPack.cmake) include(cmake/spdlogCPack.cmake)
endif () endif()

View File

@@ -11,7 +11,7 @@ Fast C++ logging library
## Install ## Install
#### Header-only version #### Header-only version
Copy the include [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler. Copy the include [folder](include/spdlog) to your build tree and use a C++11 compiler.
#### Compiled version (recommended - much faster compile times) #### Compiled version (recommended - much faster compile times)
```console ```console
@@ -19,7 +19,7 @@ $ git clone https://github.com/gabime/spdlog.git
$ cd spdlog && mkdir build && cd build $ cd spdlog && mkdir build && cd build
$ cmake .. && cmake --build . $ cmake .. && cmake --build .
``` ```
see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/CMakeLists.txt) on how to use. see example [CMakeLists.txt](example/CMakeLists.txt) on how to use.
## Platforms ## Platforms
* Linux, FreeBSD, OpenBSD, Solaris, AIX * Linux, FreeBSD, OpenBSD, Solaris, AIX
@@ -36,6 +36,7 @@ see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/
* Gentoo: `emerge dev-libs/spdlog` * Gentoo: `emerge dev-libs/spdlog`
* Arch Linux: `pacman -S spdlog` * Arch Linux: `pacman -S spdlog`
* openSUSE: `sudo zypper in spdlog-devel` * openSUSE: `sudo zypper in spdlog-devel`
* ALT Linux: `apt-get install libspdlog-devel`
* vcpkg: `vcpkg install spdlog` * vcpkg: `vcpkg install spdlog`
* conan: `conan install --requires=spdlog/[*]` * conan: `conan install --requires=spdlog/[*]`
* conda: `conda install -c conda-forge spdlog` * conda: `conda install -c conda-forge spdlog`
@@ -47,7 +48,7 @@ see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/
* Headers only or compiled * Headers only or compiled
* Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. * Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Asynchronous mode (optional) * Asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. * [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting.
* Multi/Single threaded loggers. * Multi/Single threaded loggers.
* Various log targets: * Various log targets:
* Rotating log files. * Rotating log files.
@@ -57,7 +58,7 @@ see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/
* Windows event log. * Windows event log.
* Windows debugger (```OutputDebugString(..)```). * Windows debugger (```OutputDebugString(..)```).
* Log to Qt widgets ([example](#log-to-qt-with-nice-colors)). * Log to Qt widgets ([example](#log-to-qt-with-nice-colors)).
* Easily [extendable](https://github.com/gabime/spdlog/wiki/4.-Sinks#implementing-your-own-sink) with custom log targets. * Easily [extendable](https://github.com/gabime/spdlog/wiki/Sinks#implementing-your-own-sink) with custom log targets.
* Log filtering - log levels can be modified at runtime as well as compile time. * Log filtering - log levels can be modified at runtime as well as compile time.
* Support for loading log levels from argv or environment var. * Support for loading log levels from argv or environment var.
* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display them later on demand. * [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display them later on demand.
@@ -79,7 +80,7 @@ int main()
spdlog::info("Positional args are {1} {0}..", "too", "supported"); spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned"); spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set global log level to debug spdlog::set_level(spdlog::level::debug); // Set *global* log level to debug
spdlog::debug("This message should be displayed.."); spdlog::debug("This message should be displayed..");
// change log pattern // change log pattern
@@ -223,7 +224,7 @@ void binary_example()
```c++ ```c++
// create a logger with 2 targets, with different log levels and formats. // create a logger with 2 targets, with different log levels and formats.
// The console will show only warnings or errors, while the file will log all. // The console will show only warnings or errors, while the file will log all.
void multi_sink_example() void multi_sink_example()
{ {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
@@ -240,6 +241,29 @@ void multi_sink_example()
} }
``` ```
---
#### Register several loggers - change global level
```c++
// Creation of loggers. Set levels to all registered loggers.
void set_level_example()
{
auto logger1 = spdlog::basic_logger_mt("logger1", "logs/logger1.txt");
auto logger2 = spdlog::basic_logger_mt("logger2", "logs/logger2.txt");
spdlog::set_default_logger(logger2);
spdlog::default_logger()->set_level(spdlog::level::trace); // set level for the default logger (logger2) to trace
spdlog::trace("trace message to the logger2 (specified as default)");
spdlog::set_level(spdlog::level::off) // (sic!) set level for *all* registered loggers to off (disable)
logger1.warn("warn message will not appear because the level set to off");
logger2.warn("warn message will not appear because the level set to off");
spdlog::warn("warn message will not appear because the level set to off");
}
```
--- ---
#### User-defined callbacks about log events #### User-defined callbacks about log events
```c++ ```c++
@@ -466,7 +490,7 @@ void mdc_example()
--- ---
## Benchmarks ## Benchmarks
Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz Below are some [benchmarks](bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz
#### Synchronous mode #### Synchronous mode
``` ```
@@ -518,10 +542,12 @@ Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/ben
``` ```
## Documentation ## Documentation
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki) pages.
--- ---
Thanks to [JetBrains](https://www.jetbrains.com/?from=spdlog) for donating product licenses to help develop **spdlog** <a href="https://www.jetbrains.com/?from=spdlog"><img src="logos/jetbrains-variant-4.svg" width="94" align="center" /></a> ### Powered by
<a href="https://jb.gg/OpenSource">
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo" width="200">
</a>

View File

@@ -10,7 +10,6 @@
// details/file_helper-inl.h // details/file_helper-inl.h
// details/os-inl.h // details/os-inl.h
// fmt/bundled/core.h
// fmt/bundled/posix.h // fmt/bundled/posix.h
// logger-inl.h // logger-inl.h
// sinks/daily_file_sink.h // sinks/daily_file_sink.h
@@ -23,7 +22,6 @@
// details/os-inl.h // details/os-inl.h
// details/pattern_formatter-inl.h // details/pattern_formatter-inl.h
// fmt/bundled/core.h
// fmt/bundled/format-inl.h // fmt/bundled/format-inl.h
#include <cstring> #include <cstring>
@@ -73,7 +71,6 @@
// details/registry.h // details/registry.h
// details/tcp_client-windows.h // details/tcp_client-windows.h
// details/tcp_client.h // details/tcp_client.h
// fmt/bundled/core.h
// sinks/android_sink.h // sinks/android_sink.h
// sinks/ansicolor_sink.h // sinks/ansicolor_sink.h
// sinks/basic_file_sink.h // sinks/basic_file_sink.h
@@ -150,7 +147,6 @@
// common.h // common.h
// details/fmt_helper.h // details/fmt_helper.h
// fmt/bundled/core.h
// fmt/bundled/ranges.h // fmt/bundled/ranges.h
#include <type_traits> #include <type_traits>
@@ -255,4 +251,4 @@
#include <mutex> #include <mutex>
// spdlog // spdlog
#include <spdlog/common.h> #include <spdlog/common.h>

View File

@@ -33,41 +33,41 @@ void mdc_example();
#include "spdlog/fmt/ostr.h" // support for user defined types #include "spdlog/fmt/ostr.h" // support for user defined types
int main(int, char *[]) { int main(int, char *[]) {
// Log levels can be loaded from argv/env using "SPDLOG_LEVEL"
load_levels_example();
spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR,
SPDLOG_VER_PATCH);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
spdlog::debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
spdlog::set_level(spdlog::level::info);
// Backtrace support
// Loggers can store in a ring buffer all messages (including debug/trace) for later inspection.
// When needed, call dump_backtrace() to see what happened:
spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages
for (int i = 0; i < 100; i++) {
spdlog::debug("Backtrace message {}", i); // not logged..
}
// e.g. if some error happened:
spdlog::dump_backtrace(); // log them now!
try { try {
// Log levels can be loaded from argv/env using "SPDLOG_LEVEL"
load_levels_example();
spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR,
SPDLOG_VER_PATCH);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
spdlog::debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
spdlog::set_level(spdlog::level::info);
// Backtrace support
// Loggers can store in a ring buffer all messages (including debug/trace) for later
// inspection. When needed, call dump_backtrace() to see what happened:
spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages
for (int i = 0; i < 100; i++) {
spdlog::debug("Backtrace message {}", i); // not logged..
}
// e.g. if some error happened:
spdlog::dump_backtrace(); // log them now!
stdout_logger_example(); stdout_logger_example();
basic_example(); basic_example();
rotating_example(); rotating_example();
@@ -269,7 +269,7 @@ void multi_sink_example() {
struct my_type { struct my_type {
int i = 0; int i = 0;
explicit my_type(int i) explicit my_type(int i)
: i(i){} : i(i) {}
}; };
#ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib #ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib
@@ -371,25 +371,23 @@ void replace_default_logger_example() {
// store the old logger so we don't break other examples. // store the old logger so we don't break other examples.
auto old_logger = spdlog::default_logger(); auto old_logger = spdlog::default_logger();
auto new_logger = auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/somelog.txt", true);
spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true); spdlog::set_default_logger(std::move(new_logger));
spdlog::set_default_logger(new_logger);
spdlog::set_level(spdlog::level::info); spdlog::set_level(spdlog::level::info);
spdlog::debug("This message should not be displayed!"); spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); spdlog::set_level(spdlog::level::trace);
spdlog::debug("This message should be displayed.."); spdlog::debug("This message should be displayed..");
spdlog::set_default_logger(std::move(old_logger));
spdlog::set_default_logger(old_logger);
} }
// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread local storage. // Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread
// Each thread maintains its own MDC, which loggers use to append diagnostic information to log outputs. // local storage. Each thread maintains its own MDC, which loggers use to append diagnostic
// Note: it is not supported in asynchronous mode due to its reliance on thread-local storage. // information to log outputs. Note: it is not supported in asynchronous mode due to its reliance on
// thread-local storage.
#ifndef SPDLOG_NO_TLS #ifndef SPDLOG_NO_TLS
#include "spdlog/mdc.h" #include "spdlog/mdc.h"
void mdc_example() void mdc_example() {
{
spdlog::mdc::put("key1", "value1"); spdlog::mdc::put("key1", "value1");
spdlog::mdc::put("key2", "value2"); spdlog::mdc::put("key2", "value2");
// if not using the default format, you can use the %& formatter to print mdc data as well // if not using the default format, you can use the %& formatter to print mdc data as well

View File

@@ -89,8 +89,7 @@ inline void init_thread_pool(size_t q_size,
} }
inline void init_thread_pool(size_t q_size, size_t thread_count) { inline void init_thread_pool(size_t q_size, size_t thread_count) {
init_thread_pool( init_thread_pool(q_size, thread_count, [] {}, [] {});
q_size, thread_count, [] {}, [] {});
} }
// get the global thread pool. // get the global thread pool.

View File

@@ -33,7 +33,7 @@ SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,
// send the log message to the thread pool // send the log message to the thread pool
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){ SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); pool_ptr -> post_log(shared_from_this(), msg, overflow_policy_);
} }
else { else {
throw_spdlog_ex("async log: thread pool doesn't exist anymore"); throw_spdlog_ex("async log: thread pool doesn't exist anymore");
@@ -45,7 +45,7 @@ SPDLOG_LOGGER_CATCH(msg.source)
// send flush request to the thread pool // send flush request to the thread pool
SPDLOG_INLINE void spdlog::async_logger::flush_(){ SPDLOG_INLINE void spdlog::async_logger::flush_(){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr->post_flush(shared_from_this(), overflow_policy_); pool_ptr -> post_flush(shared_from_this(), overflow_policy_);
} }
else { else {
throw_spdlog_ex("async flush: thread pool doesn't exist anymore"); throw_spdlog_ex("async flush: thread pool doesn't exist anymore");

View File

@@ -9,7 +9,6 @@
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <spdlog/details/registry.h> #include <spdlog/details/registry.h>
#include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
@@ -36,7 +35,7 @@ inline std::string &trim_(std::string &str) {
return str; return str;
} }
// return (name,value) trimmed pair from given "name=value" string. // return (name,value) trimmed pair from the given "name = value" string.
// return empty string on missing parts // return empty string on missing parts
// "key=val" => ("key", "val") // "key=val" => ("key", "val")
// " key = val " => ("key", "val") // " key = val " => ("key", "val")
@@ -55,7 +54,7 @@ inline std::pair<std::string, std::string> extract_kv_(char sep, const std::stri
return std::make_pair(trim_(k), trim_(v)); return std::make_pair(trim_(k), trim_(v));
} }
// return vector of key/value pairs from sequence of "K1=V1,K2=V2,.." // return vector of key/value pairs from a sequence of "K1=V1,K2=V2,.."
// "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...} // "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...}
inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string &str) { inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string &str) {
std::string token; std::string token;
@@ -72,7 +71,7 @@ inline std::unordered_map<std::string, std::string> extract_key_vals_(const std:
} }
SPDLOG_INLINE void load_levels(const std::string &input) { SPDLOG_INLINE void load_levels(const std::string &input) {
if (input.empty() || input.size() > 512) { if (input.empty() || input.size() >= 32768) {
return; return;
} }
@@ -82,14 +81,14 @@ SPDLOG_INLINE void load_levels(const std::string &input) {
bool global_level_found = false; bool global_level_found = false;
for (auto &name_level : key_vals) { for (auto &name_level : key_vals) {
auto &logger_name = name_level.first; const auto &logger_name = name_level.first;
auto level_name = to_lower_(name_level.second); const auto &level_name = to_lower_(name_level.second);
auto level = level::from_str(level_name); auto level = level::from_str(level_name);
// ignore unrecognized level names // ignore unrecognized level names
if (level == level::off && level_name != "off") { if (level == level::off && level_name != "off") {
continue; continue;
} }
if (logger_name.empty()) // no logger name indicate global level if (logger_name.empty()) // no logger name indicates global level
{ {
global_level_found = true; global_level_found = true;
global_level = level; global_level = level;

View File

@@ -364,7 +364,7 @@ SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view
} }
#endif #endif
#if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L #if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L
template <typename T, typename... Args> template <typename T, typename... Args>
SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view( SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view(
std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT { std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT {

View File

@@ -11,10 +11,8 @@
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <cerrno> #include <cerrno>
#include <chrono>
#include <cstdio> #include <cstdio>
#include <string> #include <string>
#include <thread>
#include <tuple> #include <tuple>
namespace spdlog { namespace spdlog {

View File

@@ -23,7 +23,6 @@
#ifdef _WIN32 #ifdef _WIN32
#include <spdlog/details/windows_include.h> #include <spdlog/details/windows_include.h>
#include <fileapi.h> // for FlushFileBuffers
#include <io.h> // for _get_osfhandle, _isatty, _fileno #include <io.h> // for _get_osfhandle, _isatty, _fileno
#include <process.h> // for _get_pid #include <process.h> // for _get_pid
@@ -265,9 +264,10 @@ SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) {
return offset; return offset;
#else #else
#if defined(sun) || defined(__sun) || defined(_AIX) || \ #if defined(sun) || defined(__sun) || defined(_AIX) || \
(defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \ (defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \
(!defined(_BSD_SOURCE) && !defined(_GNU_SOURCE)) (!defined(__APPLE__) && !defined(_BSD_SOURCE) && !defined(_GNU_SOURCE) && \
(!defined(_POSIX_VERSION) || (_POSIX_VERSION < 202405L)))
// 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris
struct helper { struct helper {
static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(),
@@ -482,13 +482,12 @@ SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target) {
} }
// find the size to allocate for the result buffer // find the size to allocate for the result buffer
int result_size = int result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0);
::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0);
if (result_size > 0) { if (result_size > 0) {
target.resize(result_size); target.resize(result_size);
result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(), result_size =
result_size); ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(), result_size);
if (result_size > 0) { if (result_size > 0) {
assert(result_size == target.size()); assert(result_size == target.size());
return; return;
@@ -563,21 +562,21 @@ SPDLOG_INLINE filename_t dir_name(const filename_t &path) {
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
} }
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4996)
#endif // _MSC_VER
std::string SPDLOG_INLINE getenv(const char *field) { std::string SPDLOG_INLINE getenv(const char *field) {
#if defined(_MSC_VER) #if defined(_MSC_VER) && defined(__cplusplus_winrt)
#if defined(__cplusplus_winrt)
return std::string{}; // not supported under uwp return std::string{}; // not supported under uwp
#else #else
size_t len = 0; char *buf = std::getenv(field);
char buf[128];
bool ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0;
return ok ? buf : std::string{};
#endif
#else // revert to getenv
char *buf = ::getenv(field);
return buf ? buf : std::string{}; return buf ? buf : std::string{};
#endif #endif
} }
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
// Do fsync by FILE handlerpointer // Do fsync by FILE handlerpointer
// Return true on success // Return true on success
@@ -592,13 +591,13 @@ SPDLOG_INLINE bool fsync(FILE *fp) {
// Do non-locking fwrite if possible by the os or use the regular locking fwrite // Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success. // Return true on success.
SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) { SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) {
#if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED) #if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED)
return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes; return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes;
#elif defined(SPDLOG_FWRITE_UNLOCKED) #elif defined(SPDLOG_FWRITE_UNLOCKED)
return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes; return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes;
#else #else
return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes; return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes;
#endif #endif
} }
} // namespace os } // namespace os

View File

@@ -54,6 +54,11 @@ SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger)
register_logger_(std::move(new_logger)); register_logger_(std::move(new_logger));
} }
SPDLOG_INLINE void registry::register_or_replace(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
register_or_replace_(std::move(new_logger));
}
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) { SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
new_logger->set_formatter(formatter_->clone()); new_logger->set_formatter(formatter_->clone());
@@ -96,7 +101,7 @@ SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() {
SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); } SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); }
// set default logger. // set default logger.
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. // the default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map.
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) { SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
if (new_default_logger != nullptr) { if (new_default_logger != nullptr) {
@@ -252,10 +257,14 @@ SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) {
} }
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) { SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) {
auto logger_name = new_logger->name(); auto &logger_name = new_logger->name();
throw_if_exists_(logger_name); throw_if_exists_(logger_name);
loggers_[logger_name] = std::move(new_logger); loggers_[logger_name] = std::move(new_logger);
} }
SPDLOG_INLINE void registry::register_or_replace_(std::shared_ptr<logger> new_logger) {
loggers_[new_logger->name()] = std::move(new_logger);
}
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

View File

@@ -31,6 +31,7 @@ public:
registry &operator=(const registry &) = delete; registry &operator=(const registry &) = delete;
void register_logger(std::shared_ptr<logger> new_logger); void register_logger(std::shared_ptr<logger> new_logger);
void register_or_replace(std::shared_ptr<logger> new_logger);
void initialize_logger(std::shared_ptr<logger> new_logger); void initialize_logger(std::shared_ptr<logger> new_logger);
std::shared_ptr<logger> get(const std::string &logger_name); std::shared_ptr<logger> get(const std::string &logger_name);
std::shared_ptr<logger> default_logger(); std::shared_ptr<logger> default_logger();
@@ -105,6 +106,7 @@ private:
void throw_if_exists_(const std::string &logger_name); void throw_if_exists_(const std::string &logger_name);
void register_logger_(std::shared_ptr<logger> new_logger); void register_logger_(std::shared_ptr<logger> new_logger);
void register_or_replace_(std::shared_ptr<logger> new_logger);
bool set_level_from_cfg_(logger *logger); bool set_level_from_cfg_(logger *logger);
std::mutex logger_map_mutex_, flusher_mutex_; std::mutex logger_map_mutex_, flusher_mutex_;
std::recursive_mutex tp_mutex_; std::recursive_mutex tp_mutex_;

View File

@@ -58,8 +58,81 @@ public:
SOCKET fd() const { return socket_; } SOCKET fd() const { return socket_; }
int connect_socket_with_timeout(SOCKET sockfd,
const struct sockaddr *addr,
int addrlen,
const timeval &tv) {
// If no timeout requested, do a normal blocking connect.
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) {
return 0;
}
return rv;
}
// Switch to nonblocking mode
u_long mode = 1UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int rv = ::connect(sockfd, addr, addrlen);
int last_error = WSAGetLastError();
if (rv == 0 || last_error == WSAEISCONN) {
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
return 0;
}
if (last_error != WSAEWOULDBLOCK) {
// Real error
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode)) {
return SOCKET_ERROR;
}
return SOCKET_ERROR;
}
// Wait until socket is writable or timeout expires
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
rv = ::select(0, nullptr, &wfds, nullptr, const_cast<timeval *>(&tv));
// Restore blocking mode regardless of select result
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (rv == 0) {
WSASetLastError(WSAETIMEDOUT);
return SOCKET_ERROR;
}
if (rv == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int so_error = 0;
int len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&so_error), &len) ==
SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (so_error != 0 && so_error != WSAEISCONN) {
// connection failed
WSASetLastError(so_error);
return SOCKET_ERROR;
}
return 0; // success
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port) { void connect(const std::string &host, int port, int timeout_ms = 0) {
if (is_connected()) { if (is_connected()) {
close(); close();
} }
@@ -71,6 +144,10 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
@@ -82,7 +159,6 @@ public:
} }
// Try each address until we successfully connect(2). // Try each address until we successfully connect(2).
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
@@ -90,18 +166,24 @@ public:
WSACleanup(); WSACleanup();
continue; continue;
} }
if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) { if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) {
last_error = 0;
break; break;
} else {
last_error = ::WSAGetLastError();
close();
} }
last_error = WSAGetLastError();
::closesocket(socket_);
socket_ = INVALID_SOCKET;
} }
::freeaddrinfo(addrinfo_result); ::freeaddrinfo(addrinfo_result);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
WSACleanup(); WSACleanup();
throw_winsock_error_("connect failed", last_error); throw_winsock_error_("connect failed", last_error);
} }
if (timeout_ms > 0) {
DWORD tv = static_cast<DWORD>(timeout_ms);
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;

View File

@@ -39,8 +39,72 @@ public:
~tcp_client() { close(); } ~tcp_client() { close(); }
int connect_socket_with_timeout(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen,
const timeval &tv) {
// Blocking connect if timeout is zero
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv < 0 && errno == EISCONN) {
// already connected, treat as success
return 0;
}
return rv;
}
// Non-blocking path
int orig_flags = ::fcntl(sockfd, F_GETFL, 0);
if (orig_flags < 0) {
return -1;
}
if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) {
return -1;
}
int rv = ::connect(sockfd, addr, addrlen);
if (rv == 0 || (rv < 0 && errno == EISCONN)) {
// immediate connect or already connected
::fcntl(sockfd, F_SETFL, orig_flags);
return 0;
}
if (errno != EINPROGRESS) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
// wait for writability
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
struct timeval tv_copy = tv;
rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy);
if (rv <= 0) {
// timeout or error
::fcntl(sockfd, F_SETFL, orig_flags);
if (rv == 0) errno = ETIMEDOUT;
return -1;
}
// check socket error
int so_error = 0;
socklen_t len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
::fcntl(sockfd, F_SETFL, orig_flags);
if (so_error != 0 && so_error != EISCONN) {
errno = so_error;
return -1;
}
return 0;
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port) { void connect(const std::string &host, int port, int timeout_ms = 0) {
close(); close();
struct addrinfo hints {}; struct addrinfo hints {};
memset(&hints, 0, sizeof(struct addrinfo)); memset(&hints, 0, sizeof(struct addrinfo));
@@ -49,6 +113,10 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
@@ -69,8 +137,9 @@ public:
last_errno = errno; last_errno = errno;
continue; continue;
} }
rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen); ::fcntl(socket_, F_SETFD, FD_CLOEXEC);
if (rv == 0) { if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) {
last_errno = 0;
break; break;
} }
last_errno = errno; last_errno = errno;
@@ -82,6 +151,12 @@ public:
throw_spdlog_ex("::connect failed", last_errno); throw_spdlog_ex("::connect failed", last_errno);
} }
if (timeout_ms > 0) {
// Set timeouts for send and recv
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),

View File

@@ -35,11 +35,10 @@ SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
size_t threads_n, size_t threads_n,
std::function<void()> on_thread_start) std::function<void()> on_thread_start)
: thread_pool(q_max_items, threads_n, on_thread_start, [] {}) {} : thread_pool(q_max_items, threads_n, std::move(on_thread_start), [] {}) {}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n)
: thread_pool( : thread_pool(q_max_items, threads_n, [] {}, [] {}) {}
q_max_items, threads_n, [] {}, [] {}) {}
// message all threads to terminate gracefully join them // message all threads to terminate gracefully join them
SPDLOG_INLINE thread_pool::~thread_pool() { SPDLOG_INLINE thread_pool::~thread_pool() {
@@ -95,8 +94,7 @@ void SPDLOG_INLINE thread_pool::worker_loop_() {
} }
// process next message in the queue // process next message in the queue
// return true if this thread should still be active (while no terminate msg // returns true if this thread should still be active (while no terminated msg was received)
// was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_() { bool SPDLOG_INLINE thread_pool::process_next_msg_() {
async_msg incoming_async_msg; async_msg incoming_async_msg;
q_.dequeue(incoming_async_msg); q_.dequeue(incoming_async_msg);

View File

@@ -142,8 +142,8 @@ struct formatter<spdlog::details::dump_info<T>, char> {
// format the given bytes range as hex // format the given bytes range as hex
template <typename FormatContext, typename Container> template <typename FormatContext, typename Container>
auto format(const spdlog::details::dump_info<Container> &the_range, FormatContext &ctx) const auto format(const spdlog::details::dump_info<Container> &the_range,
-> decltype(ctx.out()) { FormatContext &ctx) const -> decltype(ctx.out()) {
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF";
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef";
const char *hex_chars = use_uppercase ? hex_upper : hex_lower; const char *hex_chars = use_uppercase ? hex_upper : hex_lower;

View File

@@ -71,7 +71,7 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing * It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`. * into type-erased formatting functions such as `fmt::vformat`.
*/ */
template <typename Context> class dynamic_format_arg_store { FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
private: private:
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
@@ -212,7 +212,7 @@ template <typename Context> class dynamic_format_arg_store {
} }
/// Returns the number of elements in the store. /// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); } auto size() const noexcept -> size_t { return data_.size(); }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -21,7 +21,7 @@
#endif #endif
// The fmt library version in the form major * 10000 + minor * 100 + patch. // The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110103 #define FMT_VERSION 120000
// Detect compiler versions. // Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__) #if defined(__clang__) && !defined(__ibmxl__)
@@ -201,28 +201,6 @@
# define FMT_NODISCARD # define FMT_NODISCARD
#endif #endif
#ifdef FMT_DEPRECATED
// Use the provided definition.
#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated)
# define FMT_DEPRECATED [[deprecated]]
#else
# define FMT_DEPRECATED /* deprecated */
#endif
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#if FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_VISIBILITY(value) __attribute__((visibility(value))) # define FMT_VISIBILITY(value) __attribute__((visibility(value)))
#else #else
@@ -249,10 +227,32 @@
# define FMT_MSC_WARNING(...) # define FMT_MSC_WARNING(...)
#endif #endif
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
# define FMT_GCC_OPTIMIZED
#endif
FMT_PRAGMA_CLANG(diagnostic push)
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#ifndef FMT_BEGIN_NAMESPACE #ifndef FMT_BEGIN_NAMESPACE
# define FMT_BEGIN_NAMESPACE \ # define FMT_BEGIN_NAMESPACE \
namespace fmt { \ namespace fmt { \
inline namespace v11 { inline namespace v12 {
# define FMT_END_NAMESPACE \ # define FMT_END_NAMESPACE \
} \ } \
} }
@@ -294,15 +294,8 @@
#endif #endif
#define FMT_APPLY_VARIADIC(expr) \ #define FMT_APPLY_VARIADIC(expr) \
using ignore = int[]; \ using unused = int[]; \
(void)ignore { 0, (expr, 0)... } (void)unused { 0, (expr, 0)... }
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
#endif
FMT_PRAGMA_CLANG(diagnostic push)
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
@@ -325,8 +318,8 @@ using underlying_t = typename std::underlying_type<T>::type;
template <typename T> using decay_t = typename std::decay<T>::type; template <typename T> using decay_t = typename std::decay<T>::type;
using nullptr_t = decltype(nullptr); using nullptr_t = decltype(nullptr);
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 #if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION
// A workaround for gcc 4.9 to make void_t work in a SFINAE context. // A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context.
template <typename...> struct void_t_impl { template <typename...> struct void_t_impl {
using type = void; using type = void;
}; };
@@ -355,6 +348,9 @@ template <typename T> constexpr auto max_of(T a, T b) -> T {
return a > b ? a : b; return a > b ? a : b;
} }
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
namespace detail { namespace detail {
// Suppresses "unused variable" warnings with the method described in // Suppresses "unused variable" warnings with the method described in
// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
@@ -395,7 +391,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
# define FMT_ASSERT(condition, message) \ # define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
? (void)0 \ ? (void)0 \
: fmt::detail::assert_fail(__FILE__, __LINE__, (message))) : ::fmt::assert_fail(__FILE__, __LINE__, (message)))
#endif #endif
#ifdef FMT_USE_INT128 #ifdef FMT_USE_INT128
@@ -462,12 +458,13 @@ enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled };
static_assert(!FMT_UNICODE || use_utf8, static_assert(!FMT_UNICODE || use_utf8,
"Unicode support requires compiling with /utf-8"); "Unicode support requires compiling with /utf-8");
template <typename T> constexpr const char* narrow(const T*) { return nullptr; } template <typename T> constexpr auto narrow(T*) -> char* { return nullptr; }
constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* {
return s;
}
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int {
-> int {
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
for (; n != 0; ++s1, ++s2, --n) { for (; n != 0; ++s1, ++s2, --n) {
if (*s1 < *s2) return -1; if (*s1 < *s2) return -1;
@@ -526,20 +523,20 @@ template <typename Char> class basic_string_view {
constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {}
/// Constructs a string reference object from a C string and a size. /// Constructs a string view object from a C string and a size.
constexpr basic_string_view(const Char* s, size_t count) noexcept constexpr basic_string_view(const Char* s, size_t count) noexcept
: data_(s), size_(count) {} : data_(s), size_(count) {}
constexpr basic_string_view(nullptr_t) = delete; constexpr basic_string_view(nullptr_t) = delete;
/// Constructs a string reference object from a C string. /// Constructs a string view object from a C string.
#if FMT_GCC_VERSION #if FMT_GCC_VERSION
FMT_ALWAYS_INLINE FMT_ALWAYS_INLINE
#endif #endif
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value) { if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
size_ = __builtin_strlen(detail::narrow(s)); size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr.
return; return;
} }
#endif #endif
@@ -548,7 +545,7 @@ template <typename Char> class basic_string_view {
size_ = len; size_ = len;
} }
/// Constructs a string reference from a `std::basic_string` or a /// Constructs a string view from a `std::basic_string` or a
/// `std::basic_string_view` object. /// `std::basic_string_view` object.
template <typename S, template <typename S,
FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same< FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same<
@@ -585,7 +582,6 @@ template <typename Char> class basic_string_view {
return starts_with(basic_string_view<Char>(s)); return starts_with(basic_string_view<Char>(s));
} }
// Lexicographically compare this string reference to other.
FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {
int result = int result =
detail::compare(data_, other.data_, min_of(size_, other.size_)); detail::compare(data_, other.data_, min_of(size_, other.size_));
@@ -616,19 +612,6 @@ template <typename Char> class basic_string_view {
using string_view = basic_string_view<char>; using string_view = basic_string_view<char>;
/// Specifies if `T` is an extended character type. Can be specialized by users.
template <typename T> struct is_xchar : std::false_type {};
template <> struct is_xchar<wchar_t> : std::true_type {};
template <> struct is_xchar<char16_t> : std::true_type {};
template <> struct is_xchar<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_xchar<char8_t> : std::true_type {};
#endif
// DEPRECATED! Will be replaced with an alias to prevent specializations.
template <typename T> struct is_char : is_xchar<T> {};
template <> struct is_char<char> : std::true_type {};
template <typename T> class basic_appender; template <typename T> class basic_appender;
using appender = basic_appender<char>; using appender = basic_appender<char>;
@@ -740,7 +723,7 @@ class basic_specs {
}; };
unsigned data_ = 1 << fill_size_shift; unsigned data_ = 1 << fill_size_shift;
static_assert(sizeof(data_) * CHAR_BIT >= 18, ""); static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, "");
// Character (code unit) type is erased to prevent template bloat. // Character (code unit) type is erased to prevent template bloat.
char fill_data_[max_fill_size] = {' '}; char fill_data_[max_fill_size] = {' '};
@@ -781,7 +764,7 @@ class basic_specs {
(static_cast<unsigned>(p) << precision_shift); (static_cast<unsigned>(p) << precision_shift);
} }
constexpr bool dynamic() const { constexpr auto dynamic() const -> bool {
return (data_ & (width_mask | precision_mask)) != 0; return (data_ & (width_mask | precision_mask)) != 0;
} }
@@ -921,14 +904,47 @@ template <typename Char = char> class parse_context {
FMT_CONSTEXPR void check_dynamic_spec(int arg_id); FMT_CONSTEXPR void check_dynamic_spec(int arg_id);
}; };
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to std::locale to avoid the heavy <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
FMT_END_EXPORT FMT_END_EXPORT
namespace detail { namespace detail {
// Specifies if `T` is a code unit type.
template <typename T> struct is_code_unit : std::false_type {};
template <> struct is_code_unit<char> : std::true_type {};
template <> struct is_code_unit<wchar_t> : std::true_type {};
template <> struct is_code_unit<char16_t> : std::true_type {};
template <> struct is_code_unit<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_code_unit<char8_t> : bool_constant<is_utf8_enabled> {};
#endif
// Constructs fmt::basic_string_view<Char> from types implicitly convertible // Constructs fmt::basic_string_view<Char> from types implicitly convertible
// to it, deducing Char. Explicitly convertible types such as the ones returned // to it, deducing Char. Explicitly convertible types such as the ones returned
// from FMT_STRING are intentionally excluded. // from FMT_STRING are intentionally excluded.
template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)> template <typename Char, FMT_ENABLE_IF(is_code_unit<Char>::value)>
constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> { constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> {
return s; return s;
} }
@@ -1032,6 +1048,11 @@ enum {
struct view {}; struct view {};
template <typename T, typename Enable = std::true_type>
struct is_view : std::false_type {};
template <typename T>
struct is_view<T, bool_constant<sizeof(T) != 0>> : std::is_base_of<view, T> {};
template <typename Char, typename T> struct named_arg; template <typename Char, typename T> struct named_arg;
template <typename T> struct is_named_arg : std::false_type {}; template <typename T> struct is_named_arg : std::false_type {};
template <typename T> struct is_static_named_arg : std::false_type {}; template <typename T> struct is_static_named_arg : std::false_type {};
@@ -1052,11 +1073,11 @@ template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
return (B1 ? 1 : 0) + count<B2, Tail...>(); return (B1 ? 1 : 0) + count<B2, Tail...>();
} }
template <typename... Args> constexpr auto count_named_args() -> int { template <typename... T> constexpr auto count_named_args() -> int {
return count<is_named_arg<Args>::value...>(); return count<is_named_arg<T>::value...>();
} }
template <typename... Args> constexpr auto count_static_named_args() -> int { template <typename... T> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<Args>::value...>(); return count<is_static_named_arg<T>::value...>();
} }
template <typename Char> struct named_arg_info { template <typename Char> struct named_arg_info {
@@ -1064,6 +1085,16 @@ template <typename Char> struct named_arg_info {
int id; int id;
}; };
// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13.
template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index,
basic_string_view<Char> arg_name) {
for (int i = 0; i < named_arg_index; ++i) {
if (named_args[i].name == arg_name) report_error("duplicate named arg");
}
}
template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)> template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) { void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
++arg_index; ++arg_index;
@@ -1071,6 +1102,7 @@ void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>* named_args, int& arg_index, void init_named_arg(named_arg_info<Char>* named_args, int& arg_index,
int& named_arg_index, const T& arg) { int& named_arg_index, const T& arg) {
check_for_duplicate<Char>(named_args, named_arg_index, arg.name);
named_args[named_arg_index++] = {arg.name, arg_index++}; named_args[named_arg_index++] = {arg.name, arg_index++};
} }
@@ -1084,12 +1116,13 @@ template <typename T, typename Char,
FMT_ENABLE_IF(is_static_named_arg<T>::value)> FMT_ENABLE_IF(is_static_named_arg<T>::value)>
FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args, FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args,
int& arg_index, int& named_arg_index) { int& arg_index, int& named_arg_index) {
check_for_duplicate<Char>(named_args, named_arg_index, T::name);
named_args[named_arg_index++] = {T::name, arg_index++}; named_args[named_arg_index++] = {T::name, arg_index++};
} }
// To minimize the number of types we need to deal with, long is translated // To minimize the number of types we need to deal with, long is translated
// either to int or to long long depending on its size. // either to int or to long long depending on its size.
enum { long_short = sizeof(long) == sizeof(int) }; enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES };
using long_type = conditional_t<long_short, int, long long>; using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
@@ -1156,7 +1189,7 @@ template <typename Char> struct type_mapper {
static auto map(ubitint<N>) static auto map(ubitint<N>)
-> conditional_t<N <= 64, unsigned long long, void>; -> conditional_t<N <= 64, unsigned long long, void>;
template <typename T, FMT_ENABLE_IF(is_char<T>::value)> template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
static auto map(T) -> conditional_t< static auto map(T) -> conditional_t<
std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>; std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>;
@@ -1662,12 +1695,12 @@ template <typename... T> struct arg_pack {};
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES> template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
class format_string_checker { class format_string_checker {
private: private:
type types_[max_of(1, NUM_ARGS)]; type types_[max_of<size_t>(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)]; named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)];
compile_parse_context<Char> context_; compile_parse_context<Char> context_;
using parse_func = auto (*)(parse_context<Char>&) -> const Char*; using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
parse_func parse_funcs_[max_of(1, NUM_ARGS)]; parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)];
public: public:
template <typename... T> template <typename... T>
@@ -1706,7 +1739,17 @@ class format_string_checker {
-> const Char* { -> const Char* {
context_.advance_to(begin); context_.advance_to(begin);
if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_);
while (begin != end && *begin != '}') ++begin;
// If id is out of range, it means we do not know the type and cannot parse
// the format at compile time. Instead, skip over content until we finish
// the format spec, accounting for any nested replacements.
for (int bracket_count = 0;
begin != end && (bracket_count > 0 || *begin != '}'); ++begin) {
if (*begin == '{')
++bracket_count;
else if (*begin == '}')
--bracket_count;
}
return begin; return begin;
} }
@@ -2006,6 +2049,17 @@ struct has_back_insert_iterator_container_append<
.append(std::declval<InputIt>(), .append(std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {}; std::declval<InputIt>()))>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.insert(get_container(std::declval<OutputIt>()).end(),
std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
// An optimized version of std::copy with the output value type (T). // An optimized version of std::copy with the output value type (T).
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&& FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
@@ -2020,6 +2074,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value && FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append< !has_back_insert_iterator_container_append<
OutputIt, InputIt>::value &&
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)> OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt { -> OutputIt {
@@ -2029,7 +2085,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
} }
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)> FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value &&
(has_back_insert_iterator_container_append<
OutputIt, InputIt>::value ||
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)))>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
while (begin != end) *out++ = static_cast<T>(*begin++); while (begin != end) *out++ = static_cast<T>(*begin++);
return out; return out;
@@ -2149,7 +2209,7 @@ template <typename Context> class value {
static_assert(N <= 64, "unsupported _BitInt"); static_assert(N <= 64, "unsupported _BitInt");
} }
template <typename T, FMT_ENABLE_IF(is_char<T>::value)> template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) {
static_assert( static_assert(
std::is_same<T, char>::value || std::is_same<T, char_type>::value, std::is_same<T, char>::value || std::is_same<T, char_type>::value,
@@ -2225,7 +2285,7 @@ template <typename Context> class value {
custom.value = const_cast<value_type*>(&x); custom.value = const_cast<value_type*>(&x);
#endif #endif
} }
custom.format = format_custom<value_type, formatter<value_type, char_type>>; custom.format = format_custom<value_type>;
} }
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())> template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
@@ -2236,10 +2296,10 @@ template <typename Context> class value {
} }
// Formats an argument of a custom type, such as a user-defined class. // Formats an argument of a custom type, such as a user-defined class.
template <typename T, typename Formatter> template <typename T>
static void format_custom(void* arg, parse_context<char_type>& parse_ctx, static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
Context& ctx) { Context& ctx) {
auto f = Formatter(); auto f = formatter<T, char_type>();
parse_ctx.advance_to(f.parse(parse_ctx)); parse_ctx.advance_to(f.parse(parse_ctx));
using qualified_type = using qualified_type =
conditional_t<has_formatter<const T, char_type>(), const T, T>; conditional_t<has_formatter<const T, char_type>(), const T, T>;
@@ -2263,37 +2323,17 @@ template <> struct is_output_iterator<appender, char> : std::true_type {};
template <typename It, typename T> template <typename It, typename T>
struct is_output_iterator< struct is_output_iterator<
It, T, It, T,
void_t<decltype(*std::declval<decay_t<It>&>()++ = std::declval<T>())>> enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++),
: std::true_type {}; T>::value>> : std::true_type {};
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to an std::locale to avoid a heavy <locale> include.
struct locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto get() const -> Locale;
};
template <typename> constexpr auto encode_types() -> unsigned long long { template <typename> constexpr auto encode_types() -> unsigned long long {
return 0; return 0;
} }
template <typename Context, typename Arg, typename... Args> template <typename Context, typename First, typename... T>
constexpr auto encode_types() -> unsigned long long { constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) | return static_cast<unsigned>(stored_type_constant<First, Context>::value) |
(encode_types<Context, Args...>() << packed_arg_bits); (encode_types<Context, T...>() << packed_arg_bits);
} }
template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)> template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)>
@@ -2310,8 +2350,9 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC> unsigned long long DESC>
struct named_arg_store { struct named_arg_store {
// args_[0].named_args points to named_args to avoid bloating format_args. // args_[0].named_args points to named_args to avoid bloating format_args.
arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS]; arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS];
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS]; named_arg_info<typename Context::char_type>
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
template <typename... T> template <typename... T>
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
@@ -2330,8 +2371,8 @@ struct named_arg_store {
} }
named_arg_store(const named_arg_store& rhs) = delete; named_arg_store(const named_arg_store& rhs) = delete;
named_arg_store& operator=(const named_arg_store& rhs) = delete; auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete;
named_arg_store& operator=(named_arg_store&& rhs) = delete; auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete;
operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; } operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; }
}; };
@@ -2344,7 +2385,7 @@ struct format_arg_store {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
using type = using type =
conditional_t<NUM_NAMED_ARGS == 0, conditional_t<NUM_NAMED_ARGS == 0,
arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)], arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)],
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>; named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
type args; type args;
}; };
@@ -2628,22 +2669,17 @@ class context {
private: private:
appender out_; appender out_;
format_args args_; format_args args_;
FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; FMT_NO_UNIQUE_ADDRESS locale_ref loc_;
public: public:
/// The character type for the output. using char_type = char; ///< The character type for the output.
using char_type = char;
using iterator = appender; using iterator = appender;
using format_arg = basic_format_arg<context>; using format_arg = basic_format_arg<context>;
using parse_context_type FMT_DEPRECATED = parse_context<>;
template <typename T> using formatter_type FMT_DEPRECATED = formatter<T>;
enum { builtin_types = FMT_BUILTIN_TYPES }; enum { builtin_types = FMT_BUILTIN_TYPES };
/// Constructs a `context` object. References to the arguments are stored /// Constructs a `context` object. References to the arguments are stored
/// in the object so make sure they have appropriate lifetimes. /// in the object so make sure they have appropriate lifetimes.
FMT_CONSTEXPR context(iterator out, format_args args, FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {})
detail::locale_ref loc = {})
: out_(out), args_(args), loc_(loc) {} : out_(out), args_(args), loc_(loc) {}
context(context&&) = default; context(context&&) = default;
context(const context&) = delete; context(const context&) = delete;
@@ -2664,7 +2700,7 @@ class context {
// Advances the begin iterator to `it`. // Advances the begin iterator to `it`.
FMT_CONSTEXPR void advance_to(iterator) {} FMT_CONSTEXPR void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; }
}; };
template <typename Char = char> struct runtime_format_string { template <typename Char = char> struct runtime_format_string {
@@ -2702,7 +2738,7 @@ template <typename... T> struct fstring {
template <size_t N> template <size_t N>
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
using namespace detail; using namespace detail;
static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value && static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0, std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed"); "passing views as lvalues is disallowed");
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack())); if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
@@ -2729,9 +2765,9 @@ template <typename... T> struct fstring {
std::is_same<typename S::char_type, char>::value)> std::is_same<typename S::char_type, char>::value)>
FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { FMT_ALWAYS_INLINE fstring(const S&) : str(S()) {
FMT_CONSTEXPR auto sv = string_view(S()); FMT_CONSTEXPR auto sv = string_view(S());
FMT_CONSTEXPR int ignore = FMT_CONSTEXPR int unused =
(parse_format_string(sv, checker(sv, arg_pack())), 0); (parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore); detail::ignore_unused(unused);
} }
fstring(runtime_format_string<> fmt) : str(fmt.str) {} fstring(runtime_format_string<> fmt) : str(fmt.str) {}
@@ -2751,9 +2787,6 @@ template <typename T, typename Char = char>
concept formattable = is_formattable<remove_reference_t<T>, Char>::value; concept formattable = is_formattable<remove_reference_t<T>, Char>::value;
#endif #endif
template <typename T, typename Char>
using has_formatter FMT_DEPRECATED = std::is_constructible<formatter<T, Char>>;
// A formatter specialization for natively supported types. // A formatter specialization for natively supported types.
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<T, Char, struct formatter<T, Char,
@@ -2950,9 +2983,9 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
return fmt::println(stdout, fmt, static_cast<T&&>(args)...); return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
} }
FMT_END_EXPORT
FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_CLANG(diagnostic pop)
FMT_PRAGMA_GCC(pop_options) FMT_PRAGMA_GCC(pop_options)
FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef FMT_HEADER_ONLY #ifdef FMT_HEADER_ONLY

View File

@@ -22,21 +22,6 @@
#include "format.h" #include "format.h"
namespace fmt_detail {
struct time_zone {
template <typename Duration, typename T>
auto to_sys(T)
-> std::chrono::time_point<std::chrono::system_clock, Duration> {
return {};
}
};
template <typename... T> inline auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> inline void _tzset(T...) {}
} // namespace fmt_detail
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
// Enable safe chrono durations, unless explicitly disabled. // Enable safe chrono durations, unless explicitly disabled.
@@ -53,6 +38,7 @@ FMT_BEGIN_NAMESPACE
// Copyright Paul Dreik 2019 // Copyright Paul Dreik 2019
namespace safe_duration_cast { namespace safe_duration_cast {
// DEPRECATED!
template <typename To, typename From, template <typename To, typename From,
FMT_ENABLE_IF(!std::is_same<From, To>::value && FMT_ENABLE_IF(!std::is_same<From, To>::value &&
std::numeric_limits<From>::is_signed == std::numeric_limits<From>::is_signed ==
@@ -176,17 +162,6 @@ auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
int& ec) -> To { int& ec) -> To {
using From = std::chrono::duration<FromRep, FromPeriod>; using From = std::chrono::duration<FromRep, FromPeriod>;
ec = 0; 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 // 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: // to count() in the To type, by multiplying it with this:
@@ -297,8 +272,6 @@ namespace detail {
#define FMT_NOMACRO #define FMT_NOMACRO
template <typename T = void> struct null {}; template <typename T = void> struct null {};
inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
inline auto localtime_s(...) -> null<> { return null<>(); }
inline auto gmtime_r(...) -> null<> { return null<>(); } inline auto gmtime_r(...) -> null<> { return null<>(); }
inline auto gmtime_s(...) -> null<> { return null<>(); } inline auto gmtime_s(...) -> null<> { return null<>(); }
@@ -341,7 +314,7 @@ inline auto get_classic_locale() -> const std::locale& {
} }
template <typename CodeUnit> struct codecvt_result { template <typename CodeUnit> struct codecvt_result {
static constexpr const size_t max_size = 32; static constexpr size_t max_size = 32;
CodeUnit buf[max_size]; CodeUnit buf[max_size];
CodeUnit* end; CodeUnit* end;
}; };
@@ -435,14 +408,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
} }
template <typename Rep1, typename Rep2> template <typename T, typename U>
struct is_same_arithmetic_type using is_similar_arithmetic_type =
: public std::integral_constant<bool, bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
(std::is_integral<Rep1>::value && (std::is_floating_point<T>::value &&
std::is_integral<Rep2>::value) || std::is_floating_point<U>::value)>;
(std::is_floating_point<Rep1>::value &&
std::is_floating_point<Rep2>::value)> {
};
FMT_NORETURN inline void throw_duration_error() { FMT_NORETURN inline void throw_duration_error() {
FMT_THROW(format_error("cannot format duration")); FMT_THROW(format_error("cannot format duration"));
@@ -461,11 +431,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
using common_rep = typename std::common_type<FromRep, typename To::rep, using common_rep = typename std::common_type<FromRep, typename To::rep,
decltype(factor::num)>::type; decltype(factor::num)>::type;
common_rep count = from.count(); // This conversion is lossless.
int ec = 0;
auto count = safe_duration_cast::lossless_integral_conversion<common_rep>(
from.count(), ec);
if (ec) throw_duration_error();
// Multiply from.count() by factor and check for overflow. // Multiply from.count() by factor and check for overflow.
if (const_check(factor::num != 1)) { if (const_check(factor::num != 1)) {
@@ -476,6 +442,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
count *= factor::num; count *= factor::num;
} }
if (const_check(factor::den != 1)) count /= factor::den; if (const_check(factor::den != 1)) count /= factor::den;
int ec = 0;
auto to = auto to =
To(safe_duration_cast::lossless_integral_conversion<typename To::rep>( To(safe_duration_cast::lossless_integral_conversion<typename To::rep>(
count, ec)); count, ec));
@@ -489,6 +456,8 @@ template <typename To, typename FromRep, typename FromPeriod,
std::is_floating_point<typename To::rep>::value)> std::is_floating_point<typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#if FMT_SAFE_DURATION_CAST #if FMT_SAFE_DURATION_CAST
// Preserve infinity and NaN.
if (!isfinite(from.count())) return static_cast<To>(from.count());
// Throwing version of safe_duration_cast is only available for // Throwing version of safe_duration_cast is only available for
// integer to integer or float to float casts. // integer to integer or float to float casts.
int ec; int ec;
@@ -501,11 +470,11 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#endif #endif
} }
template < template <typename To, typename FromRep, typename FromPeriod,
typename To, typename FromRep, typename FromPeriod, FMT_ENABLE_IF(
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)> !is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
// Mixed integer <-> float cast is not supported by safe_duration_cast. // Mixed integer <-> float cast is not supported by safe duration_cast.
return std::chrono::duration_cast<To>(from); return std::chrono::duration_cast<To>(from);
} }
@@ -519,68 +488,10 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
.count(); .count();
} }
// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without
// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160.
template <typename T> FMT_CONSTEXPR auto has_current_zone() -> bool {
using namespace std::chrono;
using namespace fmt_detail;
return !std::is_same<decltype(current_zone()), fmt_detail::time_zone*>::value;
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
/**
* Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in local time. Unlike `std::localtime`, this function is
* thread-safe on most platforms.
*/
inline auto localtime(std::time_t time) -> std::tm {
struct dispatcher {
std::time_t time_;
std::tm tm_;
inline dispatcher(std::time_t t) : time_(t) {}
inline auto run() -> bool {
using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_));
}
inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_));
}
inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION
inline auto fallback(detail::null<>) -> bool {
using namespace fmt::detail;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
}
#endif
};
dispatcher lt(time);
// Too big time values may be unsupported.
if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
return lt.tm_;
}
#if FMT_USE_LOCAL_TIME
template <typename Duration,
FMT_ENABLE_IF(detail::has_current_zone<Duration>())>
inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
using namespace std::chrono;
using namespace fmt_detail;
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
}
#endif
/** /**
* Converts given time since epoch as `std::time_t` value into calendar time, * Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this
@@ -652,7 +563,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
// Add ASCII '0' to each digit byte and insert separators. // Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
constexpr const size_t len = 8; constexpr size_t len = 8;
if (const_check(is_big_endian())) { if (const_check(is_big_endian())) {
char tmp[len]; char tmp[len];
std::memcpy(tmp, &digits, len); std::memcpy(tmp, &digits, len);
@@ -911,7 +822,14 @@ template <typename Derived> struct null_chrono_spec_handler {
FMT_CONSTEXPR void on_tz_name() { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); }
}; };
struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> { class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
private:
bool has_timezone_ = false;
public:
constexpr explicit tm_format_checker(bool has_timezone)
: has_timezone_(has_timezone) {}
FMT_NORETURN inline void unsupported() { FMT_NORETURN inline void unsupported() {
FMT_THROW(format_error("no format")); FMT_THROW(format_error("no format"));
} }
@@ -949,8 +867,12 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_utc_offset(numeric_system) {} FMT_CONSTEXPR void on_utc_offset(numeric_system) {
FMT_CONSTEXPR void on_tz_name() {} if (!has_timezone_) FMT_THROW(format_error("no timezone"));
}
FMT_CONSTEXPR void on_tz_name() {
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
}
}; };
inline auto tm_wday_full_name(int wday) -> const char* { inline auto tm_wday_full_name(int wday) -> const char* {
@@ -980,24 +902,27 @@ inline auto tm_mon_short_name(int mon) -> const char* {
} }
template <typename T, typename = void> template <typename T, typename = void>
struct has_member_data_tm_gmtoff : std::false_type {}; struct has_tm_gmtoff : std::false_type {};
template <typename T> template <typename T>
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> struct has_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> : std::true_type {};
: std::true_type {};
template <typename T, typename = void> template <typename T, typename = void> struct has_tm_zone : std::false_type {};
struct has_member_data_tm_zone : std::false_type {};
template <typename T> template <typename T>
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>> struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
: std::true_type {};
inline void tzset_once() { template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
static bool init = []() { auto set_tm_zone(T& time, char* tz) -> bool {
using namespace fmt_detail; time.tm_zone = tz;
_tzset(); return true;
return false; }
}(); template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
ignore_unused(init); auto set_tm_zone(T&, char*) -> bool {
return false;
}
inline auto utc() -> char* {
static char tz[] = "UTC";
return tz;
} }
// Converts value to Int and checks that it's in the range [0, upper). // Converts value to Int and checks that it's in the range [0, upper).
@@ -1005,7 +930,7 @@ template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline auto to_nonnegative_int(T value, Int upper) -> Int { inline auto to_nonnegative_int(T value, Int upper) -> Int {
if (!std::is_unsigned<Int>::value && if (!std::is_unsigned<Int>::value &&
(value < 0 || to_unsigned(value) > to_unsigned(upper))) { (value < 0 || to_unsigned(value) > to_unsigned(upper))) {
FMT_THROW(fmt::format_error("chrono value is out of range")); FMT_THROW(format_error("chrono value is out of range"));
} }
return static_cast<Int>(value); return static_cast<Int>(value);
} }
@@ -1090,7 +1015,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
// Format subseconds which are given as a floating point type with an // Format subseconds which are given as a floating point type with an
// appropriate number of digits. We cannot pass the Duration here, as we // appropriate number of digits. We cannot pass the Duration here, as we
// explicitly need to pass the Rep value in the chrono_formatter. // explicitly need to pass the Rep value in the duration_formatter.
template <typename Duration> template <typename Duration>
void write_floating_seconds(memory_buffer& buf, Duration duration, void write_floating_seconds(memory_buffer& buf, Duration duration,
int num_fractional_digits = -1) { int num_fractional_digits = -1) {
@@ -1124,7 +1049,7 @@ class tm_writer {
static constexpr int days_per_week = 7; static constexpr int days_per_week = 7;
const std::locale& loc_; const std::locale& loc_;
const bool is_classic_; bool is_classic_;
OutputIt out_; OutputIt out_;
const Duration* subsecs_; const Duration* subsecs_;
const std::tm& tm_; const std::tm& tm_;
@@ -1160,8 +1085,8 @@ class tm_writer {
} }
auto tm_hour12() const noexcept -> int { auto tm_hour12() const noexcept -> int {
const auto h = tm_hour(); auto h = tm_hour();
const auto z = h < 12 ? h : h - 12; auto z = h < 12 ? h : h - 12;
return z == 0 ? 12 : z; return z == 0 ? 12 : z;
} }
@@ -1177,11 +1102,11 @@ class tm_writer {
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
auto iso_year_weeks(long long curr_year) const noexcept -> int { auto iso_year_weeks(long long curr_year) const noexcept -> int {
const auto prev_year = curr_year - 1; auto prev_year = curr_year - 1;
const auto curr_p = auto curr_p =
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
days_per_week; days_per_week;
const auto prev_p = auto prev_p =
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
days_per_week; days_per_week;
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
@@ -1191,15 +1116,15 @@ class tm_writer {
days_per_week; days_per_week;
} }
auto tm_iso_week_year() const noexcept -> long long { auto tm_iso_week_year() const noexcept -> long long {
const auto year = tm_year(); auto year = tm_year();
const auto w = iso_week_num(tm_yday(), tm_wday()); auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return year - 1; if (w < 1) return year - 1;
if (w > iso_year_weeks(year)) return year + 1; if (w > iso_year_weeks(year)) return year + 1;
return year; return year;
} }
auto tm_iso_week_of_year() const noexcept -> int { auto tm_iso_week_of_year() const noexcept -> int {
const auto year = tm_year(); auto year = tm_year();
const auto w = iso_week_num(tm_yday(), tm_wday()); auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return iso_year_weeks(year - 1); if (w < 1) return iso_year_weeks(year - 1);
if (w > iso_year_weeks(year)) return 1; if (w > iso_year_weeks(year)) return 1;
return w; return w;
@@ -1236,9 +1161,8 @@ class tm_writer {
uint32_or_64_or_128_t<long long> n = to_unsigned(year); uint32_or_64_or_128_t<long long> n = to_unsigned(year);
const int num_digits = count_digits(n); const int num_digits = count_digits(n);
if (negative && pad == pad_type::zero) *out_++ = '-'; if (negative && pad == pad_type::zero) *out_++ = '-';
if (width > num_digits) { if (width > num_digits)
out_ = detail::write_padding(out_, pad, width - num_digits); out_ = detail::write_padding(out_, pad, width - num_digits);
}
if (negative && pad != pad_type::zero) *out_++ = '-'; if (negative && pad != pad_type::zero) *out_++ = '-';
out_ = format_decimal<Char>(out_, n, num_digits); out_ = format_decimal<Char>(out_, n, num_digits);
} }
@@ -1259,45 +1183,22 @@ class tm_writer {
write2(static_cast<int>(offset % 60)); write2(static_cast<int>(offset % 60));
} }
template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(has_tm_gmtoff<T>::value)>
void format_utc_offset_impl(const T& tm, numeric_system ns) { void format_utc_offset(const T& tm, numeric_system ns) {
write_utc_offset(tm.tm_gmtoff, ns); write_utc_offset(tm.tm_gmtoff, ns);
} }
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(!has_tm_gmtoff<T>::value)>
void format_utc_offset_impl(const T& tm, numeric_system ns) { void format_utc_offset(const T&, numeric_system ns) {
#if defined(_WIN32) && defined(_UCRT) write_utc_offset(0, ns);
tzset_once();
long offset = 0;
_get_timezone(&offset);
if (tm.tm_isdst) {
long dstbias = 0;
_get_dstbias(&dstbias);
offset += dstbias;
}
write_utc_offset(-offset, ns);
#else
if (ns == numeric_system::standard) return format_localized('z');
// Extract timezone offset from timezone conversion functions.
std::tm gtm = tm;
std::time_t gt = std::mktime(&gtm);
std::tm ltm = gmtime(gt);
std::time_t lt = std::mktime(&ltm);
long long offset = gt - lt;
write_utc_offset(offset, ns);
#endif
} }
template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
void format_tz_name_impl(const T& tm) { void format_tz_name(const T& tm) {
if (is_classic_) out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
else
format_localized('Z');
} }
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
void format_tz_name_impl(const T&) { void format_tz_name(const T&) {
format_localized('Z'); out_ = std::copy_n(utc(), 3, out_);
} }
void format_localized(char format, char modifier = 0) { void format_localized(char format, char modifier = 0) {
@@ -1408,8 +1309,8 @@ class tm_writer {
out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_); out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);
} }
void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); }
void on_tz_name() { format_tz_name_impl(tm_); } void on_tz_name() { format_tz_name(tm_); }
void on_year(numeric_system ns, pad_type pad) { void on_year(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) if (is_classic_ || ns == numeric_system::standard)
@@ -1483,11 +1384,10 @@ class tm_writer {
void on_day_of_year(pad_type pad) { void on_day_of_year(pad_type pad) {
auto yday = tm_yday() + 1; auto yday = tm_yday() + 1;
auto digit1 = yday / 100; auto digit1 = yday / 100;
if (digit1 != 0) { if (digit1 != 0)
write1(digit1); write1(digit1);
} else { else
out_ = detail::write_padding(out_, pad); out_ = detail::write_padding(out_, pad);
}
write2(yday % 100, pad); write2(yday % 100, pad);
} }
@@ -1624,18 +1524,16 @@ template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_integral<Rep>::value)> FMT_ENABLE_IF(std::is_integral<Rep>::value)>
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d) inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
-> std::chrono::duration<Rep, std::milli> { -> std::chrono::duration<Rep, std::milli> {
// this may overflow and/or the result may not fit in the // This may overflow and/or the result may not fit in the target type.
// target type.
#if FMT_SAFE_DURATION_CAST #if FMT_SAFE_DURATION_CAST
using CommonSecondsType = using common_seconds_type =
typename std::common_type<decltype(d), std::chrono::seconds>::type; typename std::common_type<decltype(d), std::chrono::seconds>::type;
const auto d_as_common = detail::duration_cast<CommonSecondsType>(d); auto d_as_common = detail::duration_cast<common_seconds_type>(d);
const auto d_as_whole_seconds = auto d_as_whole_seconds =
detail::duration_cast<std::chrono::seconds>(d_as_common); detail::duration_cast<std::chrono::seconds>(d_as_common);
// this conversion should be nonproblematic // This conversion should be nonproblematic.
const auto diff = d_as_common - d_as_whole_seconds; auto diff = d_as_common - d_as_whole_seconds;
const auto ms = auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
return ms; return ms;
#else #else
auto s = detail::duration_cast<std::chrono::seconds>(d); auto s = detail::duration_cast<std::chrono::seconds>(d);
@@ -1707,32 +1605,28 @@ class get_locale {
} }
}; };
template <typename FormatContext, typename OutputIt, typename Rep, template <typename Char, typename Rep, typename Period>
typename Period> struct duration_formatter {
struct chrono_formatter { using iterator = basic_appender<Char>;
FormatContext& context; iterator out;
OutputIt out;
int precision;
bool localized = false;
// rep is unsigned to avoid overflow. // rep is unsigned to avoid overflow.
using rep = using rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
unsigned, typename make_unsigned_or_unchanged<Rep>::type>; unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
rep val; rep val;
int precision;
locale_ref locale;
bool localized = false;
using seconds = std::chrono::duration<rep>; using seconds = std::chrono::duration<rep>;
seconds s; seconds s;
using milliseconds = std::chrono::duration<rep, std::milli>; using milliseconds = std::chrono::duration<rep, std::milli>;
bool negative; bool negative;
using char_type = typename FormatContext::char_type; using tm_writer_type = tm_writer<iterator, Char>;
using tm_writer_type = tm_writer<OutputIt, char_type>;
chrono_formatter(FormatContext& ctx, OutputIt o, duration_formatter(iterator o, std::chrono::duration<Rep, Period> d,
std::chrono::duration<Rep, Period> d) locale_ref loc)
: context(ctx), : out(o), val(static_cast<rep>(d.count())), locale(loc), negative(false) {
out(o),
val(static_cast<rep>(d.count())),
negative(false) {
if (d.count() < 0) { if (d.count() < 0) {
val = 0 - val; val = 0 - val;
negative = true; negative = true;
@@ -1746,19 +1640,16 @@ struct chrono_formatter {
// returns true if nan or inf, writes to out. // returns true if nan or inf, writes to out.
auto handle_nan_inf() -> bool { auto handle_nan_inf() -> bool {
if (isfinite(val)) { if (isfinite(val)) return false;
return false;
}
if (isnan(val)) { if (isnan(val)) {
write_nan(); write_nan();
return true; return true;
} }
// must be +-inf // must be +-inf
if (val > 0) { if (val > 0)
write_pinf(); std::copy_n("inf", 3, out);
} else { else
write_ninf(); std::copy_n("-inf", 4, out);
}
return true; return true;
} }
@@ -1786,10 +1677,9 @@ struct chrono_formatter {
} }
void write_sign() { void write_sign() {
if (negative) { if (!negative) return;
*out++ = '-'; *out++ = '-';
negative = false; negative = false;
}
} }
void write(Rep value, int width, pad_type pad = pad_type::zero) { void write(Rep value, int width, pad_type pad = pad_type::zero) {
@@ -1801,24 +1691,22 @@ struct chrono_formatter {
if (width > num_digits) { if (width > num_digits) {
out = detail::write_padding(out, pad, width - num_digits); out = detail::write_padding(out, pad, width - num_digits);
} }
out = format_decimal<char_type>(out, n, num_digits); out = format_decimal<Char>(out, n, num_digits);
} }
void write_nan() { std::copy_n("nan", 3, out); } void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }
template <typename Callback, typename... Args> template <typename Callback, typename... Args>
void format_tm(const tm& time, Callback cb, Args... args) { void format_tm(const tm& time, Callback cb, Args... args) {
if (isnan(val)) return write_nan(); if (isnan(val)) return write_nan();
get_locale loc(localized, context.locale()); get_locale loc(localized, locale);
auto w = tm_writer_type(loc, out, time); auto w = tm_writer_type(loc, out, time);
(w.*cb)(args...); (w.*cb)(args...);
out = w.out(); out = w.out();
} }
void on_text(const char_type* begin, const char_type* end) { void on_text(const Char* begin, const Char* end) {
copy<char_type>(begin, end, out); copy<Char>(begin, end, out);
} }
// These are not implemented because durations don't have date information. // These are not implemented because durations don't have date information.
@@ -1888,13 +1776,12 @@ struct chrono_formatter {
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val), write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
precision); precision);
if (negative) *out++ = '-'; if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') { if (buf.size() < 2 || buf[1] == '.')
out = detail::write_padding(out, pad); out = detail::write_padding(out, pad);
} out = copy<Char>(buf.begin(), buf.end(), out);
out = copy<char_type>(buf.begin(), buf.end(), out);
} else { } else {
write(second(), 2, pad); write(second(), 2, pad);
write_fractional_seconds<char_type>( write_fractional_seconds<Char>(
out, std::chrono::duration<rep, Period>(val), precision); out, std::chrono::duration<rep, Period>(val), precision);
} }
return; return;
@@ -1936,12 +1823,10 @@ struct chrono_formatter {
void on_duration_value() { void on_duration_value() {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
write_sign(); write_sign();
out = format_duration_value<char_type>(out, val, precision); out = format_duration_value<Char>(out, val, precision);
} }
void on_duration_unit() { void on_duration_unit() { out = format_duration_unit<Char, Period>(out); }
out = format_duration_unit<char_type, Period>(out);
}
}; };
} // namespace detail } // namespace detail
@@ -2011,12 +1896,11 @@ class year_month_day {
constexpr auto month() const noexcept -> fmt::month { return month_; } constexpr auto month() const noexcept -> fmt::month { return month_; }
constexpr auto day() const noexcept -> fmt::day { return day_; } constexpr auto day() const noexcept -> fmt::day { return day_; }
}; };
#endif #endif // __cpp_lib_chrono >= 201907
template <typename Char> template <typename Char>
struct formatter<weekday, Char> : private formatter<std::tm, Char> { struct formatter<weekday, Char> : private formatter<std::tm, Char> {
private: private:
bool localized_ = false;
bool use_tm_formatter_ = false; bool use_tm_formatter_ = false;
public: public:
@@ -2024,8 +1908,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
++it; ++it;
localized_ = true; this->set_localized();
return it;
} }
use_tm_formatter_ = it != end && *it != '}'; use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it; return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2036,7 +1919,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto time = std::tm(); auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding()); time.tm_wday = static_cast<int>(wd.c_encoding());
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx); if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(localized_, ctx.locale()); detail::get_locale loc(this->localized(), ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time); auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_weekday(); w.on_abbr_weekday();
return w.out(); return w.out();
@@ -2070,7 +1953,6 @@ struct formatter<day, Char> : private formatter<std::tm, Char> {
template <typename Char> template <typename Char>
struct formatter<month, Char> : private formatter<std::tm, Char> { struct formatter<month, Char> : private formatter<std::tm, Char> {
private: private:
bool localized_ = false;
bool use_tm_formatter_ = false; bool use_tm_formatter_ = false;
public: public:
@@ -2078,8 +1960,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
++it; ++it;
localized_ = true; this->set_localized();
return it;
} }
use_tm_formatter_ = it != end && *it != '}'; use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it; return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2090,7 +1971,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto time = std::tm(); auto time = std::tm();
time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1; time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx); if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(localized_, ctx.locale()); detail::get_locale loc(this->localized(), ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time); auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_month(); w.on_abbr_month();
return w.out(); return w.out();
@@ -2154,7 +2035,6 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
format_specs specs_; format_specs specs_;
detail::arg_ref<Char> width_ref_; detail::arg_ref<Char> width_ref_;
detail::arg_ref<Char> precision_ref_; detail::arg_ref<Char> precision_ref_;
bool localized_ = false;
basic_string_view<Char> fmt_; basic_string_view<Char> fmt_;
public: public:
@@ -2177,7 +2057,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); it = detail::parse_precision(it, end, specs_, precision_ref_, ctx);
} }
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
localized_ = true; specs_.set_localized();
++it; ++it;
} }
end = detail::parse_chrono_format(it, end, checker); end = detail::parse_chrono_format(it, end, checker);
@@ -2204,11 +2084,10 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
out = detail::format_duration_value<Char>(out, d.count(), precision); out = detail::format_duration_value<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out); detail::format_duration_unit<Char, Period>(out);
} else { } else {
using chrono_formatter = auto f =
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>; detail::duration_formatter<Char, Rep, Period>(out, d, ctx.locale());
auto f = chrono_formatter(ctx, out, d);
f.precision = precision; f.precision = precision;
f.localized = localized_; f.localized = specs_.localized();
detail::parse_chrono_format(begin, end, f); detail::parse_chrono_format(begin, end, f);
} }
return detail::write( return detail::write(
@@ -2220,30 +2099,15 @@ template <typename Char> struct formatter<std::tm, Char> {
private: private:
format_specs specs_; format_specs specs_;
detail::arg_ref<Char> width_ref_; detail::arg_ref<Char> width_ref_;
basic_string_view<Char> fmt_ =
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
protected: protected:
basic_string_view<Char> fmt_; auto localized() const -> bool { return specs_.localized(); }
FMT_CONSTEXPR void set_localized() { specs_.set_localized(); }
template <typename Duration, typename FormatContext> FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone)
auto do_format(const std::tm& tm, FormatContext& ctx, -> const Char* {
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = ctx.locale();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w =
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@@ -2256,12 +2120,41 @@ template <typename Char> struct formatter<std::tm, Char> {
if (it == end) return it; if (it == end) return it;
} }
end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); if (*it == 'L') {
specs_.set_localized();
++it;
}
end = detail::parse_chrono_format(it, end,
detail::tm_format_checker(has_timezone));
// Replace the default format string only if the new spec is not empty. // Replace the default format string only if the new spec is not empty.
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
return end; return end;
} }
template <typename Duration, typename FormatContext>
auto do_format(const std::tm& tm, FormatContext& ctx,
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = specs.localized() ? ctx.locale() : locale_ref();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, detail::has_tm_gmtoff<std::tm>::value);
}
template <typename FormatContext> template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
@@ -2269,10 +2162,11 @@ template <typename Char> struct formatter<std::tm, Char> {
} }
}; };
// DEPRECATED! Reversed order of template parameters.
template <typename Char, typename Duration> template <typename Char, typename Duration>
struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> { struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>(); return this->do_parse(ctx, true);
} }
template <typename FormatContext> template <typename FormatContext>
@@ -2283,6 +2177,7 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
if (detail::const_check( if (detail::const_check(
period::num == 1 && period::den == 1 && period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value)) { !std::is_floating_point<typename Duration::rep>::value)) {
detail::set_tm_zone(tm, detail::utc());
return formatter<std::tm, Char>::format(tm, ctx); return formatter<std::tm, Char>::format(tm, ctx);
} }
Duration epoch = val.time_since_epoch(); Duration epoch = val.time_since_epoch();
@@ -2290,11 +2185,13 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
epoch - detail::duration_cast<std::chrono::seconds>(epoch)); epoch - detail::duration_cast<std::chrono::seconds>(epoch));
if (subsecs.count() < 0) { if (subsecs.count() < 0) {
auto second = detail::duration_cast<Duration>(std::chrono::seconds(1)); auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));
if (tm.tm_sec != 0) if (tm.tm_sec != 0) {
--tm.tm_sec; --tm.tm_sec;
else } else {
tm = gmtime(val - second); tm = gmtime(val - second);
subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1)); detail::set_tm_zone(tm, detail::utc());
}
subsecs += second;
} }
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs); return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);
} }
@@ -2312,23 +2209,29 @@ struct formatter<utc_time<Duration>, Char>
}; };
template <typename Duration, typename Char> template <typename Duration, typename Char>
struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> { struct formatter<local_time<Duration>, Char>
FMT_CONSTEXPR formatter() { : private formatter<std::tm, Char> {
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>(); FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return this->do_parse(ctx, false);
} }
template <typename FormatContext> template <typename FormatContext>
auto format(local_time<Duration> val, FormatContext& ctx) const auto format(local_time<Duration> val, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto time_since_epoch = val.time_since_epoch();
auto seconds_since_epoch =
detail::duration_cast<std::chrono::seconds>(time_since_epoch);
// Use gmtime to prevent time zone conversion since local_time has an
// unspecified time zone.
std::tm t = gmtime(seconds_since_epoch.count());
using period = typename Duration::period; using period = typename Duration::period;
if (period::num == 1 && period::den == 1 && if (period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value) { !std::is_floating_point<typename Duration::rep>::value) {
return formatter<std::tm, Char>::format(localtime(val), ctx); return formatter<std::tm, Char>::format(t, ctx);
} }
auto epoch = val.time_since_epoch(); auto subsecs =
auto subsecs = detail::duration_cast<Duration>( detail::duration_cast<Duration>(time_since_epoch - seconds_since_epoch);
epoch - detail::duration_cast<std::chrono::seconds>(epoch)); return formatter<std::tm, Char>::do_format(t, ctx, &subsecs);
return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
} }
}; };

View File

@@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip. // Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb { struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} constexpr rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex) constexpr rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex) constexpr rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF), : r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {} b(uint32_t(hex) & 0xFF) {}
@@ -205,97 +205,135 @@ struct rgb {
namespace detail { namespace detail {
// color is a struct of either a rgb color or a terminal color. // A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
struct color_type { struct color_type {
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} constexpr color_type() noexcept = default;
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { constexpr color_type(color rgb_color) noexcept
value.rgb_color = static_cast<uint32_t>(rgb_color); : value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
} }
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | constexpr auto value() const noexcept -> uint32_t {
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; return value_ & 0xFFFFFF;
} }
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} { constexpr color_type(uint32_t value) noexcept : value_(value) {}
value.term_color = static_cast<uint8_t>(term_color);
} uint32_t value_ = 0;
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
}; };
} // namespace detail } // namespace detail
/// A text style consisting of foreground and background colors and emphasis. /// A text style consisting of foreground and background colors and emphasis.
class text_style { class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public: public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {} : style_(static_cast<uint64_t>(em) << 54) {}
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (!set_foreground_color) { if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
set_foreground_color = rhs.set_foreground_color; report_error("can't OR a terminal color");
foreground_color = rhs.foreground_color; style_ |= rhs.style_;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
report_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
report_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this; return *this;
} }
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
-> text_style { -> text_style {
return lhs |= rhs; return lhs |= rhs;
} }
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
return style_ == rhs.style_;
}
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
return !(*this == rhs);
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color; return (style_ & (1 << 24)) != 0;
} }
FMT_CONSTEXPR auto has_background() const noexcept -> bool { FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color; return (style_ & (1ULL << 51)) != 0;
} }
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0; return (style_ >> 54) != 0;
} }
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style"); FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color; return style_ & 0x3FFFFFF;
} }
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style"); FMT_ASSERT(has_background(), "no background specified for this style");
return background_color; return (style_ >> 27) & 0x3FFFFFF;
} }
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems; return static_cast<emphasis>(style_ >> 54);
} }
private: private:
FMT_CONSTEXPR text_style(bool is_foreground, FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style; -> text_style;
@@ -303,23 +341,19 @@ class text_style {
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style; -> text_style;
detail::color_type foreground_color; uint64_t style_ = 0;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
}; };
/// Creates a text style from the foreground (text) color. /// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style { -> text_style {
return text_style(true, foreground); return foreground.value_;
} }
/// Creates a text style from the background color. /// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style { -> text_style {
return text_style(false, background); return static_cast<uint64_t>(background.value_) << 27;
} }
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@@ -334,37 +368,35 @@ template <typename Char> struct ansi_color_escape {
const char* esc) noexcept { const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code // If we have a terminal color, we need to output another escape code
// sequence. // sequence.
if (!text_color.is_rgb) { if (text_color.is_terminal_color()) {
bool is_background = esc == string_view("\x1b[48;2;"); bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color; uint32_t value = text_color.value();
// Background ASCII codes are the same as the foreground ones but with // Background ASCII codes are the same as the foreground ones but with
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) { if (value >= 100u) {
buffer[index++] = static_cast<Char>('1'); buffer[size++] = static_cast<Char>('1');
value %= 100u; value %= 100u;
} }
buffer[index++] = static_cast<Char>('0' + value / 10u); buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u); buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m'); buffer[size++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return; return;
} }
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]); buffer[i] = static_cast<Char>(esc[i]);
} }
rgb color(text_color.value.rgb_color); rgb color(text_color.value());
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0); size = 19;
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {}; uint8_t em_codes[num_emphases] = {};
@@ -377,26 +409,28 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('['); buffer[size++] = static_cast<Char>(';');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
} }
buffer[index++] = static_cast<Char>(0);
buffer[size - 1] = static_cast<Char>('m');
} }
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size(); return buffer + size;
} }
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u]; Char buffer[7u + 4u * num_emphases];
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept { char delimiter) noexcept {
@@ -441,32 +475,26 @@ template <typename T> struct styled_arg : view {
}; };
template <typename Char> template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts, void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) { basic_format_args<buffered_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true;
auto emphasis = make_emphasis<Char>(ts.get_emphasis()); auto emphasis = make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end()); buf.append(emphasis.begin(), emphasis.end());
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true;
auto foreground = make_foreground_color<Char>(ts.get_foreground()); auto foreground = make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end()); buf.append(foreground.begin(), foreground.end());
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true;
auto background = make_background_color<Char>(ts.get_background()); auto background = make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
vformat_to(buf, fmt, args); vformat_to(buf, fmt, args);
if (has_style) reset_color<Char>(buf); if (ts != text_style()) reset_color<Char>(buf);
} }
} // namespace detail } // namespace detail
inline void vprint(FILE* f, const text_style& ts, string_view fmt, inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
format_args args) {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
@@ -482,8 +510,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(FILE* f, const text_style& ts, format_string<T...> fmt, void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
T&&... args) {
vprint(f, ts, fmt.str, vargs<T...>{{args...}}); vprint(f, ts, fmt.str, vargs<T...>{{args...}});
} }
@@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(const text_style& ts, format_string<T...> fmt, T&&... args) { void print(text_style ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...); return print(stdout, ts, fmt, std::forward<T>(args)...);
} }
inline auto vformat(const text_style& ts, string_view fmt, format_args args) inline auto vformat(text_style ts, string_view fmt, format_args args)
-> std::string { -> std::string {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
@@ -521,7 +548,7 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
* ``` * ```
*/ */
template <typename... T> template <typename... T>
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args) inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
-> std::string { -> std::string {
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}}); return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
} }
@@ -529,8 +556,8 @@ inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
/// Formats a string with the given text_style and writes the output to `out`. /// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt, template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
format_args args) -> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<char>(out); auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
*/ */
template <typename OutputIt, typename... T, template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, const text_style& ts, inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
format_string<T...> fmt, T&&... args) -> OutputIt { T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}}); return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
} }

View File

@@ -19,8 +19,6 @@ FMT_BEGIN_NAMESPACE
// A compile-time string which is compiled into fast formatting code. // A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {}; FMT_EXPORT class compiled_string {};
namespace detail {
template <typename S> template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {}; struct is_compiled_string : std::is_base_of<compiled_string, S> {};
@@ -41,28 +39,40 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
# define FMT_COMPILE(s) FMT_STRING(s) # define FMT_COMPILE(s) FMT_STRING(s)
#endif #endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str> inline namespace literals {
struct udl_compiled_string : compiled_string { template <detail::fixed_string Str> constexpr auto operator""_cf() {
using char_type = Char; return FMT_COMPILE(Str.data);
constexpr explicit operator basic_string_view<char_type>() const { }
return {Str.data, N - 1}; } // namespace literals
}
};
#endif #endif
namespace detail {
template <typename T, typename... Tail> template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& { constexpr auto first(const T& value, const Tail&...) -> const T& {
return value; return value;
} }
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args> template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first, constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) { [[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
@@ -94,8 +104,8 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
} }
template <typename Char, typename... Args> template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name, constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) { type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name); return get_arg_index_by_name<Args...>(name);
} }
@@ -115,8 +125,8 @@ template <typename Char> struct text {
basic_string_view<Char> data; basic_string_view<Char> data;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data); return write<Char>(out, data);
} }
}; };
@@ -125,8 +135,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {}; struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char> template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos, constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
size_t size) { -> text<Char> {
return {{&s[pos], size}}; return {{&s[pos], size}};
} }
@@ -134,8 +144,8 @@ template <typename Char> struct code_unit {
Char value; Char value;
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&...) const { constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
*out++ = value; *out++ = value;
return out; return out;
} }
@@ -143,7 +153,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = detail::get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
@@ -156,13 +166,13 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {}; struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N. // A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field { template <typename Char, typename V, int N> struct field {
using char_type = Char; using char_type = Char;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const T& arg = get_arg_checked<T, N>(args...); const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) { if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg); auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out); return copy<Char>(s.begin(), s.end(), out);
} else { } else {
@@ -180,10 +190,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name; basic_string_view<Char> name;
template <typename OutputIt, typename T> template <typename OutputIt, typename T>
constexpr static bool try_format_argument( constexpr static auto try_format_argument(
OutputIt& out, OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) { [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) { if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) { if (arg_name == arg.name) {
out = write<Char>(out, arg.value); out = write<Char>(out, arg.value);
@@ -193,8 +203,8 @@ template <typename Char> struct runtime_named_field {
return false; return false;
} }
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
FMT_THROW(format_error("argument with specified name is not found")); FMT_THROW(format_error("argument with specified name is not found"));
@@ -207,17 +217,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {}; struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers. // A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field { template <typename Char, typename V, int N> struct spec_field {
using char_type = Char; using char_type = Char;
formatter<T, Char> fmt; formatter<V, Char> fmt;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr FMT_INLINE OutputIt format(OutputIt out, constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
const Args&... args) const { -> OutputIt {
const auto& vargs = const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...); fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs); basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx); return fmt.format(get_arg_checked<V, N>(args...), ctx);
} }
}; };
@@ -229,8 +239,8 @@ template <typename L, typename R> struct concat {
R rhs; R rhs;
using char_type = typename L::char_type; using char_type = typename L::char_type;
template <typename OutputIt, typename... Args> template <typename OutputIt, typename... T>
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...); out = lhs.format(out, args...);
return rhs.format(out, args...); return rhs.format(out, args...);
} }
@@ -240,14 +250,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {}; struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R> template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) { constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs}; return {lhs, rhs};
} }
struct unknown_format {}; struct unknown_format {};
template <typename Char> template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) { constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) { for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break; if (str[pos] == '{' || str[pos] == '}') break;
} }
@@ -280,8 +290,8 @@ template <typename T, typename Char> struct parse_specs_result {
enum { manual_indexing_id = -1 }; enum { manual_indexing_id = -1 };
template <typename T, typename Char> template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str, constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
size_t pos, int next_arg_id) { int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos); str.remove_prefix(pos);
auto ctx = auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id); compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
@@ -295,16 +305,16 @@ template <typename Char> struct arg_id_handler {
arg_id_kind kind; arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int on_auto() { constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing"); FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0; return 0;
} }
constexpr int on_index(int id) { constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index; kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
constexpr int on_name(basic_string_view<Char> id) { constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name; kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
@@ -425,7 +435,7 @@ constexpr auto compile_format_string(S fmt) {
} }
template <typename... Args, typename S, template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) { constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt); constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) { if constexpr (str.size() == 0) {
@@ -443,27 +453,28 @@ FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const Args&... args) { const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>(); auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...); cf.format(std::back_inserter(s), args...);
return s; return s;
} }
template <typename OutputIt, typename CompiledFormat, typename... Args, template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)> FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) { const T&... args) -> OutputIt {
return cf.format(out, args...); return cf.format(out, args...);
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&, FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
Args&&... args) { -> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) { if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S()); constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
@@ -476,74 +487,97 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
} }
} }
} }
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format( return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()), static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format(compiled, std::forward<Args>(args)...); return fmt::format(compiled, std::forward<T>(args)...);
} }
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) { detail::unknown_format>()) {
return fmt::format_to( return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()), out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...); std::forward<T>(args)...);
} else { } else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...); return fmt::format_to(out, compiled, std::forward<T>(args)...);
} }
} }
#endif #endif
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...); fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
-> size_t {
auto buf = detail::counting_buffer<>(); auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count(); return buf.count();
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) { void print(std::FILE* f, const S& fmt, T&&... args) {
auto buf = memory_buffer(); auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, args...); fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buf.data(), buf.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... T,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) { void print(const S& fmt, T&&... args) {
print(stdout, fmt, args...); print(stdout, fmt, std::forward<T>(args)...);
} }
#if FMT_USE_NONTYPE_TEMPLATE_ARGS template <size_t N> class static_format_result {
inline namespace literals { private:
template <detail::fixed_string Str> constexpr auto operator""_cf() { char data[N];
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t), public:
Str>(); template <typename S, typename... T,
} FMT_ENABLE_IF(is_compiled_string<S>::value)>
} // namespace literals explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
#endif *fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
auto str() const -> fmt::string_view { return {data, N - 1}; }
auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

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

View File

@@ -22,7 +22,7 @@
#include "format.h" #include "format.h"
#if FMT_USE_LOCALE #if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale> # include <locale>
#endif #endif
@@ -31,14 +31,49 @@
#endif #endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail {
#ifndef FMT_CUSTOM_ASSERT_FAIL
FMT_FUNC void assert_fail(const char* file, int line, const char* message) { FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when // Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails. // writing to stderr fails.
fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort(); abort();
} }
#endif
#if FMT_USE_LOCALE
namespace detail {
using std::locale;
using std::numpunct;
using std::use_facet;
} // namespace detail
template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, "");
}
#else
namespace detail {
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
} // namespace detail
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
using namespace detail;
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
namespace detail {
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code, FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) noexcept { string_view message) noexcept {
@@ -79,33 +114,6 @@ inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
#if FMT_USE_LOCALE
using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, locale>::value, "");
}
#else
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
template <typename Char> template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> { FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>()); auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
@@ -133,14 +141,13 @@ FMT_FUNC auto write_loc(appender out, loc_value value,
} // namespace detail } // namespace detail
FMT_FUNC void report_error(const char* message) { FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS #if FMT_MSC_VERSION || defined(__NVCC__)
// Use FMT_THROW instead of throw to avoid bogus unreachable code warnings // Silence unreachable code warnings in MSVC and NVCC because these
// from MSVC. // are nearly impossible to fix in a generic code.
FMT_THROW(format_error(message)); volatile bool b = true;
#else if (!b) return;
fputs(message, stderr);
abort();
#endif #endif
FMT_THROW(format_error(message));
} }
template <typename Locale> typename Locale::id format_facet<Locale>::id; template <typename Locale> typename Locale::id format_facet<Locale>::id;
@@ -174,11 +181,11 @@ inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
} }
// Compilers should be able to optimize this into the ror instruction. // Compilers should be able to optimize this into the ror instruction.
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
r &= 31; r &= 31;
return (n >> r) | (n << (32 - r)); return (n >> r) | (n << (32 - r));
} }
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
r &= 63; r &= 63;
return (n >> r) | (n << (64 - r)); return (n >> r) | (n << (64 - r));
} }
@@ -212,7 +219,7 @@ inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
return (e * 631305 - 261663) >> 21; return (e * 631305 - 261663) >> 21;
} }
FMT_INLINE_VARIABLE constexpr struct { FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct {
uint32_t divisor; uint32_t divisor;
int shift_amount; int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; } div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@@ -275,7 +282,7 @@ template <> struct cache_accessor<float> {
static auto get_cached_power(int k) noexcept -> uint64_t { static auto get_cached_power(int k) noexcept -> uint64_t {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range"); "k is out of range");
static constexpr const uint64_t pow10_significands[] = { static constexpr uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@@ -370,7 +377,7 @@ template <> struct cache_accessor<double> {
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k, FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range"); "k is out of range");
static constexpr const uint128_fallback pow10_significands[] = { static constexpr uint128_fallback pow10_significands[] = {
#if FMT_USE_FULL_CACHE_DRAGONBOX #if FMT_USE_FULL_CACHE_DRAGONBOX
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0x9faacf3df73609b1, 0x77b191618c54e9ad}, {0x9faacf3df73609b1, 0x77b191618c54e9ad},
@@ -1037,7 +1044,7 @@ template <> struct cache_accessor<double> {
#if FMT_USE_FULL_CACHE_DRAGONBOX #if FMT_USE_FULL_CACHE_DRAGONBOX
return pow10_significands[k - float_info<double>::min_k]; return pow10_significands[k - float_info<double>::min_k];
#else #else
static constexpr const uint64_t powers_of_5_64[] = { static constexpr uint64_t powers_of_5_64[] = {
0x0000000000000001, 0x0000000000000005, 0x0000000000000019, 0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
@@ -1097,7 +1104,7 @@ template <> struct cache_accessor<double> {
return {r.high(), r.low() == 0}; return {r.high(), r.low() == 0};
} }
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
-> uint32_t { -> uint32_t {
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta)); return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
} }
@@ -1149,8 +1156,8 @@ auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
exponent <= case_shorter_interval_left_endpoint_upper_threshold; exponent <= case_shorter_interval_left_endpoint_upper_threshold;
} }
// Remove trailing zeros from n and return the number of zeros removed (float) // Remove trailing zeros from n and return the number of zeros removed (float).
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int {
FMT_ASSERT(n != 0, ""); FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
constexpr uint32_t mod_inv_5 = 0xcccccccd; constexpr uint32_t mod_inv_5 = 0xcccccccd;
@@ -1170,22 +1177,19 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
return s; return s;
} }
// Removes trailing zeros and returns the number of zeros removed (double) // Removes trailing zeros and returns the number of zeros removed (double).
FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int {
FMT_ASSERT(n != 0, ""); FMT_ASSERT(n != 0, "");
// This magic number is ceil(2^90 / 10^8).
constexpr uint64_t magic_number = 12379400392853802749ull;
auto nm = umul128(n, magic_number);
// Is n is divisible by 10^8? // Is n is divisible by 10^8?
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { constexpr uint32_t ten_pow_8 = 100000000u;
if ((n % ten_pow_8) == 0) {
// If yes, work with the quotient... // If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64)); auto n32 = static_cast<uint32_t>(n / ten_pow_8);
// ... and use the 32 bit variant of the function // ... and use the 32 bit variant of the function
int s = remove_trailing_zeros(n32, 8); int num_zeros = remove_trailing_zeros(n32, 8);
n = n32; n = n32;
return s; return num_zeros;
} }
// If n is not divisible by 10^8, work with n itself. // If n is not divisible by 10^8, work with n itself.
@@ -1210,7 +1214,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// The main algorithm for shorter interval case // The main algorithm for shorter interval case
template <typename T> template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept { FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp<T> {
decimal_fp<T> ret_value; decimal_fp<T> ret_value;
// Compute k and beta // Compute k and beta
const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
@@ -1454,8 +1458,8 @@ FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
auto out = appender(buf); auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}")) if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out}); return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string( parse_format_string(fmt,
fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}}); format_handler<>{parse_context<>(fmt), {out, args, loc}});
} }
template <typename T> struct span { template <typename T> struct span {
@@ -1526,9 +1530,8 @@ template <typename F> class glibc_file : public file_base<F> {
} }
void init_buffer() { void init_buffer() {
if (this->file_->_IO_write_ptr) return; if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return;
// Force buffer initialization by placing and removing a char in a buffer. // Force buffer initialization by placing and removing a char in a buffer.
assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);
putc_unlocked(0, this->file_); putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr; --this->file_->_IO_write_ptr;
} }
@@ -1547,10 +1550,11 @@ template <typename F> class glibc_file : public file_base<F> {
void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; }
bool needs_flush() const { auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false; if ((this->file_->_flags & line_buffered) == 0) return false;
char* end = this->file_->_IO_write_end; char* end = this->file_->_IO_write_end;
return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); auto size = max_of<ptrdiff_t>(this->file_->_IO_write_ptr - end, 0);
return memchr(end, '\n', static_cast<size_t>(size));
} }
void flush() { fflush_unlocked(this->file_); } void flush() { fflush_unlocked(this->file_); }
@@ -1574,7 +1578,7 @@ template <typename F> class apple_file : public file_base<F> {
void init_buffer() { void init_buffer() {
if (this->file_->_p) return; if (this->file_->_p) return;
// Force buffer initialization by placing and removing a char in a buffer. // Force buffer initialization by placing and removing a char in a buffer.
putc_unlocked(0, this->file_); if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_);
--this->file_->_p; --this->file_->_p;
++this->file_->_w; ++this->file_->_w;
} }
@@ -1595,7 +1599,7 @@ template <typename F> class apple_file : public file_base<F> {
this->file_->_w -= size; this->file_->_w -= size;
} }
bool needs_flush() const { auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false; if ((this->file_->_flags & line_buffered) == 0) return false;
return memchr(this->file_->_p + this->file_->_w, '\n', return memchr(this->file_->_p + this->file_->_w, '\n',
to_unsigned(-this->file_->_w)); to_unsigned(-this->file_->_w));

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,8 @@
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \ (!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
!defined(__wasm__)
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
# else # else

View File

@@ -33,8 +33,8 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag // Generate a unique explicit instantiation in every translation unit using a
// type in an anonymous namespace. // tag type in an anonymous namespace.
namespace { namespace {
struct file_access_tag {}; struct file_access_tag {};
} // namespace } // namespace
@@ -158,7 +158,8 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
FMT_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) { void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...)); fmt::print(os, FMT_STRING("{}\n"),
fmt::format(fmt, std::forward<T>(args)...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -9,7 +9,7 @@
#define FMT_PRINTF_H_ #define FMT_PRINTF_H_
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <algorithm> // std::max # include <algorithm> // std::find
# include <limits> // std::numeric_limits # include <limits> // std::numeric_limits
#endif #endif
@@ -18,10 +18,6 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context { template <typename Char> class basic_printf_context {
private: private:
basic_appender<Char> out_; basic_appender<Char> out_;
@@ -33,8 +29,6 @@ template <typename Char> class basic_printf_context {
public: public:
using char_type = Char; using char_type = Char;
using parse_context_type = parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 }; enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are /// Constructs a `printf_context` object. References to the arguments are
@@ -46,7 +40,7 @@ template <typename Char> class basic_printf_context {
auto out() -> basic_appender<Char> { return out_; } auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {} void advance_to(basic_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; } auto locale() -> locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> { auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id); return args_.get(id);
@@ -74,10 +68,9 @@ inline auto find<false, char>(const char* first, const char* last, char value,
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IS_SIGNED> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool { template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = to_unsigned(max_value<int>()); return value <= to_unsigned(max_value<int>());
return value <= max;
} }
inline static auto fits_in_int(bool) -> bool { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
@@ -95,7 +88,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int { auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big"); report_error("number is too big");
return (std::max)(static_cast<int>(value), 0); return max_of(static_cast<int>(value), 0);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@@ -410,7 +403,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
arg_index = parse_ctx.next_arg_id(); arg_index = parse_ctx.next_arg_id();
else else
parse_ctx.check_arg_id(--arg_index); parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index); auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
}; };
const Char* start = parse_ctx.begin(); const Char* start = parse_ctx.begin();
@@ -571,15 +566,19 @@ inline auto vsprintf(basic_string_view<Char> fmt,
* *
* std::string message = fmt::sprintf("The answer is %d", 42); * std::string message = fmt::sprintf("The answer is %d", 42);
*/ */
template <typename S, typename... T, typename Char = detail::char_t<S>> template <typename... T>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { inline auto sprintf(string_view fmt, const T&... args) -> std::string {
return vsprintf(detail::to_string_view(fmt), return vsprintf(fmt, make_printf_args(args...));
fmt::make_format_args<basic_printf_context<Char>>(args...)); }
template <typename... T>
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
} }
template <typename Char> template <typename Char>
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt, auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int { typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args); detail::vprintf(buf, fmt, args);
size_t size = buf.size(); size_t size = buf.size();
@@ -596,17 +595,14 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
* *
* fmt::fprintf(stderr, "Don't %s!", "panic"); * fmt::fprintf(stderr, "Don't %s!", "panic");
*/ */
template <typename S, typename... T, typename Char = detail::char_t<S>> template <typename... T>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt), return vfprintf(f, fmt, make_printf_args(args...));
make_printf_args<Char>(args...));
} }
template <typename... T>
template <typename Char> FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt, const T&... args) -> int {
typename vprintf_args<Char>::type args) return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
-> int {
return vfprintf(stdout, fmt, args);
} }
/** /**
@@ -621,11 +617,6 @@ template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int { inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...)); return vfprintf(stdout, fmt, make_printf_args(args...));
} }
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -11,7 +11,6 @@
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <initializer_list> # include <initializer_list>
# include <iterator> # include <iterator>
# include <string>
# include <tuple> # include <tuple>
# include <type_traits> # include <type_traits>
# include <utility> # include <utility>
@@ -31,7 +30,7 @@ template <typename T> class is_map {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -40,17 +39,16 @@ template <typename T> class is_set {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
}; };
// C array overload // C array overload
template <typename T, std::size_t N> template <typename T, size_t N>
auto range_begin(const T (&arr)[N]) -> const T* { auto range_begin(const T (&arr)[N]) -> const T* {
return arr; return arr;
} }
template <typename T, std::size_t N> template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
auto range_end(const T (&arr)[N]) -> const T* {
return arr + N; return arr + N;
} }
@@ -120,7 +118,7 @@ template <typename T> class is_tuple_like_ {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -154,7 +152,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value> template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ { class is_tuple_formattable_ {
public: public:
static constexpr const bool value = false; static constexpr bool value = false;
}; };
template <typename T, typename C> class is_tuple_formattable_<T, C, true> { template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is> template <size_t... Is>
@@ -170,7 +168,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{})); C>::value)...>{}));
public: public:
static constexpr const bool value = static constexpr bool value =
decltype(check(tuple_index_sequence<T>{}))::value; decltype(check(tuple_index_sequence<T>{}))::value;
}; };
@@ -208,7 +206,7 @@ template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>; using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get; using std::get;
template <typename Tuple, typename Char, std::size_t... Is> template <typename Tuple, typename Char, size_t... Is>
auto get_formatters(index_sequence<Is...>) auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>; -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple } // namespace tuple
@@ -219,7 +217,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>())); using type = decltype(*detail::range_begin(std::declval<R&>()));
}; };
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> { template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
using type = T&; using type = T&;
}; };
@@ -281,14 +279,15 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like { template <typename T> struct is_tuple_like {
static constexpr const bool value = static constexpr bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value; detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
}; };
FMT_EXPORT
template <typename T, typename C> struct is_tuple_formattable { template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value = static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
detail::is_tuple_formattable_<T, C>::value;
}; };
template <typename Tuple, typename Char> template <typename Tuple, typename Char>
@@ -343,8 +342,9 @@ struct formatter<Tuple, Char,
} }
}; };
FMT_EXPORT
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static constexpr const bool value = static constexpr bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value; detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
}; };
@@ -368,6 +368,7 @@ template <typename P1, typename... Pn>
struct conjunction<P1, Pn...> struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void> template <typename T, typename Char, typename Enable = void>
struct range_formatter; struct range_formatter;
@@ -670,7 +671,8 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
}; };
template <typename Char, typename Tuple> struct tuple_join_view : detail::view { FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple; const Tuple& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
@@ -685,15 +687,15 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0 # define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif #endif
template <typename Char, typename Tuple> template <typename Tuple, typename Char>
struct formatter<tuple_join_view<Char, Tuple>, Char, struct formatter<tuple_join_view<Tuple, Char>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> { enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>()); return do_parse(ctx, std::tuple_size<Tuple>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, Tuple>& value, auto format(const tuple_join_view<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>()); return do_format(value, ctx, std::tuple_size<Tuple>());
} }
@@ -725,14 +727,14 @@ struct formatter<tuple_join_view<Char, Tuple>, Char,
} }
template <typename FormatContext> template <typename FormatContext>
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx, auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const -> std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, size_t N> template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
using std::get; using std::get;
@@ -754,7 +756,7 @@ template <typename T> class is_container_adaptor_like {
template <typename> static void check(...); template <typename> static void check(...);
public: public:
static constexpr const bool value = static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value; !std::is_void<decltype(check<T>(nullptr))>::value;
}; };
@@ -774,13 +776,13 @@ struct formatter<
: formatter<detail::all<typename T::container_type>, Char> { : formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>; using all = detail::all<typename T::container_type>;
template <typename FormatContext> template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T { struct getter : T {
static auto get(const T& t) -> all { static auto get(const T& v) -> all {
return {t.*(&getter::c)}; // Access c through the derived class. return {v.*(&getter::c)}; // Access c through the derived class.
} }
}; };
return formatter<all>::format(getter::get(t), ctx); return formatter<all>::format(getter::get(value), ctx);
} }
}; };
@@ -825,7 +827,7 @@ auto join(Range&& r, string_view sep)
*/ */
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> { -> tuple_join_view<Tuple, char> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@@ -15,15 +15,13 @@
# include <atomic> # include <atomic>
# include <bitset> # include <bitset>
# include <complex> # include <complex>
# include <cstdlib>
# include <exception> # include <exception>
# include <functional> # include <functional> // std::reference_wrapper
# include <memory> # include <memory>
# include <thread> # include <thread>
# include <type_traits> # include <type_traits>
# include <typeinfo> # include <typeinfo> // std::type_info
# include <utility> # include <utility> // std::make_index_sequence
# include <vector>
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L # if FMT_CPLUSPLUS >= 201703L
@@ -62,27 +60,26 @@
# endif # endif
#endif #endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined. #ifdef FMT_CPP_LIB_FILESYSTEM
#ifndef FMT_CPP_LIB_FILESYSTEM // Use the provided definition.
# ifdef __cpp_lib_filesystem #elif defined(__cpp_lib_filesystem)
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem # define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
# else #else
# define FMT_CPP_LIB_FILESYSTEM 0 # define FMT_CPP_LIB_FILESYSTEM 0
# endif
#endif #endif
#ifndef FMT_CPP_LIB_VARIANT #ifdef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant // Use the provided definition.
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant #elif defined(__cpp_lib_variant)
# else # define FMT_CPP_LIB_VARIANT __cpp_lib_variant
# define FMT_CPP_LIB_VARIANT 0 #else
# endif # define FMT_CPP_LIB_VARIANT 0
#endif #endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM #if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p, auto get_path_string(const std::filesystem::path& p,
@@ -111,9 +108,168 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
} }
} }
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail } // namespace detail
FMT_EXPORT template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
template <typename Char> struct formatter<std::filesystem::path, Char> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; format_specs specs_;
@@ -163,40 +319,20 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
} }
}; };
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE template <size_t N, typename Char>
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> { : nested_formatter<basic_string_view<Char>, Char> {
private: private:
// Functor because C++11 doesn't support generic lambdas. // This is a functor because C++11 doesn't support generic lambdas.
struct writer { struct writer {
const std::bitset<N>& bs; const std::bitset<N>& bs;
template <typename OutputIt> template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) { for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0')); out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out; return out;
} }
}; };
@@ -209,14 +345,10 @@ struct formatter<std::bitset<N>, Char>
} }
}; };
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::optional<T>, Char, struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> { std::enable_if_t<is_formattable<T, Char>::value>> {
@@ -255,31 +387,9 @@ struct formatter<std::optional<T>, Char,
return detail::write(out, ')'); return detail::write(out, ')');
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_optional #endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char> template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char, struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value || std::enable_if_t<(std::is_void<T>::value ||
@@ -306,12 +416,9 @@ struct formatter<std::expected<T, E>, Char,
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // __cpp_lib_expected #endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location #ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> { template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
@@ -329,45 +436,14 @@ template <> struct formatter<std::source_location> {
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif #endif
#if FMT_CPP_LIB_VARIANT #if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like { template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value; static constexpr bool value = detail::is_variant_like_<T>::value;
}; };
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> { template <typename Char> struct formatter<std::monostate, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin(); return ctx.begin();
@@ -380,12 +456,11 @@ template <typename Char> struct formatter<std::monostate, Char> {
} }
}; };
FMT_EXPORT
template <typename Variant, typename Char> template <typename Variant, typename Char>
struct formatter< struct formatter<Variant, Char,
Variant, Char, std::enable_if_t<std::conjunction_v<
std::enable_if_t<std::conjunction_v< is_variant_like<Variant>,
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> { detail::is_variant_formattable<Variant, Char>>>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin(); return ctx.begin();
} }
@@ -410,15 +485,14 @@ struct formatter<
return out; return out;
} }
}; };
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::error_code> { template <> struct formatter<std::error_code> {
private: private:
format_specs specs_; format_specs specs_;
detail::arg_ref<char> width_ref_; detail::arg_ref<char> width_ref_;
bool debug_ = false;
public: public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
@@ -426,11 +500,19 @@ template <> struct formatter<std::error_code> {
if (it == end) return it; if (it == end) return it;
it = detail::parse_align(it, end, specs_); it = detail::parse_align(it, end, specs_);
if (it == end) return it;
char c = *it; char c = *it;
if ((c >= '0' && c <= '9') || c == '{') if (it != end && ((c >= '0' && c <= '9') || c == '{'))
it = detail::parse_width(it, end, specs_, width_ref_, ctx); it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && *it == 's') {
specs_.set_type(presentation_type::string);
++it;
}
return it; return it;
} }
@@ -440,113 +522,48 @@ template <> struct formatter<std::error_code> {
auto specs = specs_; auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
memory_buffer buf; auto buf = memory_buffer();
buf.append(string_view(ec.category().name())); if (specs_.type() == presentation_type::string) {
buf.push_back(':'); buf.append(ec.message());
detail::write<char>(appender(buf), ec.value()); } else {
return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()), buf.append(string_view(ec.category().name()));
specs); buf.push_back(':');
detail::write<char>(appender(buf), ec.value());
}
auto quoted = memory_buffer();
auto str = string_view(buf.data(), buf.size());
if (debug_) {
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
str = string_view(quoted.data(), quoted.size());
}
return detail::write<char>(ctx.out(), str, specs);
} }
}; };
#if FMT_USE_RTTI #if FMT_USE_RTTI
namespace detail { template <> struct formatter<std::type_info> {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
FMT_EXPORT
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
public: public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
return ctx.begin(); return ctx.begin();
} }
template <typename Context> template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return detail::write_demangled_name<Char>(ctx.out(), ti); return detail::write_demangled_name(ctx.out(), ti);
} }
}; };
#endif #endif // FMT_USE_RTTI
FMT_EXPORT template <typename T>
template <typename T, typename Char>
struct formatter< struct formatter<
T, Char, // DEPRECATED! Mixing code unit types. T, char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> { typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private: private:
bool with_typename_ = false; bool with_typename_ = false;
public: public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@@ -563,47 +580,18 @@ struct formatter<
auto out = ctx.out(); auto out = ctx.out();
#if FMT_USE_RTTI #if FMT_USE_RTTI
if (with_typename_) { if (with_typename_) {
out = detail::write_demangled_name<Char>(out, typeid(ex)); out = detail::write_demangled_name(out, typeid(ex));
*out++ = ':'; *out++ = ':';
*out++ = ' '; *out++ = ' ';
} }
#endif #endif
return detail::write_bytes<Char>(out, string_view(ex.what())); return detail::write_bytes<char>(out, string_view(ex.what()));
} }
}; };
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and // We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N // std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization. // in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char> template <typename BitRef, typename Char>
struct formatter<BitRef, Char, struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>> enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
@@ -615,15 +603,6 @@ struct formatter<BitRef, Char,
} }
}; };
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::atomic<T>, Char, struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>> enable_if_t<is_formattable<T, Char>::value>>
@@ -636,7 +615,6 @@ struct formatter<std::atomic<T>, Char,
}; };
#ifdef __cpp_lib_atomic_flag_test #ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> { struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext> template <typename FormatContext>
@@ -647,7 +625,6 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename T, typename Char> struct formatter<std::complex<T>, Char> { template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private: private:
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;
@@ -710,10 +687,13 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
}; };
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char, struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>> // Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> { : formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext> template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
@@ -723,4 +703,5 @@ struct formatter<std::reference_wrapper<T>, Char,
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_STD_H_ #endif // FMT_STD_H_

View File

@@ -55,6 +55,16 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif #endif
return false; return false;
} }
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
@@ -112,14 +122,6 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif
template <typename... T> template <typename... T>
constexpr auto make_wformat_args(T&... args) constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) { -> decltype(fmt::make_format_args<wformat_context>(args...)) {
@@ -155,13 +157,13 @@ auto join(std::initializer_list<T> list, wstring_view sep)
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> { -> tuple_join_view<Tuple, wchar_t> {
return {tuple, sep}; return {tuple, sep};
} }
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> fmt, auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, fmt, args); detail::vformat_to(buf, fmt, args);
@@ -191,24 +193,20 @@ auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, template <typename S, typename Char = detail::format_string_char_t<S>,
typename Char = detail::format_string_char_t<S>, FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& inline auto vformat(locale_ref loc, const S& fmt,
detail::is_exotic_char<Char>::value)> basic_format_args<buffered_context<Char>> args)
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
detail::locale_ref(loc));
return {buf.data(), buf.size()}; return {buf.data(), buf.size()};
} }
template <typename Locale, typename S, typename... T, template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_locale<Locale>::value&& FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto format(locale_ref loc, const S& fmt, T&&... args)
inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt), return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
@@ -219,7 +217,7 @@ template <typename OutputIt, typename S,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& fmt, auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt { basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args); detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@@ -235,27 +233,24 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename Locale, typename S, typename OutputIt, typename... Args, template <typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt, basic_format_args<buffered_context<Char>> args)
typename detail::vformat_args<Char>::type args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc)); vformat_to(buf, detail::to_string_view(fmt), args, loc);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename Locale, typename OutputIt, typename S, typename... T, template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value && bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value> detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, const Locale& loc, const S& fmt, inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
T&&... args) -> -> typename std::enable_if<enable, OutputIt>::type {
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt), return vformat_to(out, loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
@@ -264,7 +259,7 @@ template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt, inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) basic_format_args<buffered_context<Char>> args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
@@ -322,7 +317,7 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...)); return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
} }
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
-> std::wstring { -> std::wstring {
auto buf = wmemory_buffer(); auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
@@ -330,23 +325,11 @@ inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
} }
template <typename... T> template <typename... T>
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args) inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring { -> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
wformat_string<T...> fmt, const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>(); auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);

View File

@@ -20,11 +20,7 @@
#ifndef FMT_USE_WINDOWS_H #ifndef FMT_USE_WINDOWS_H
#define FMT_USE_WINDOWS_H 0 #define FMT_USE_WINDOWS_H 0
#endif #endif
#include <spdlog/fmt/bundled/core.h>
#include <spdlog/fmt/bundled/format.h> #include <spdlog/fmt/bundled/format.h>
#else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib #else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib
#include <fmt/core.h>
#include <fmt/format.h> #include <fmt/format.h>
#endif #endif

View File

@@ -57,7 +57,7 @@ SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT {
std::swap(tracer_, other.tracer_); std::swap(tracer_, other.tracer_);
} }
SPDLOG_INLINE void swap(logger &a, logger &b) { a.swap(b); } SPDLOG_INLINE void swap(logger &a, logger &b) noexcept { a.swap(b); }
SPDLOG_INLINE void logger::set_level(level::level_enum log_level) { level_.store(log_level); } SPDLOG_INLINE void logger::set_level(level::level_enum log_level) { level_.store(log_level); }
@@ -163,12 +163,12 @@ SPDLOG_INLINE void logger::dump_backtrace_() {
} }
} }
SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) { SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) const {
auto flush_level = flush_level_.load(std::memory_order_relaxed); auto flush_level = flush_level_.load(std::memory_order_relaxed);
return (msg.level >= flush_level) && (msg.level != level::off); return (msg.level >= flush_level) && (msg.level != level::off);
} }
SPDLOG_INLINE void logger::err_handler_(const std::string &msg) { SPDLOG_INLINE void logger::err_handler_(const std::string &msg) const {
if (custom_err_handler_) { if (custom_err_handler_) {
custom_err_handler_(msg); custom_err_handler_(msg);
} else { } else {

View File

@@ -363,14 +363,14 @@ protected:
virtual void sink_it_(const details::log_msg &msg); virtual void sink_it_(const details::log_msg &msg);
virtual void flush_(); virtual void flush_();
void dump_backtrace_(); void dump_backtrace_();
bool should_flush_(const details::log_msg &msg); bool should_flush_(const details::log_msg &msg) const;
// handle errors during logging. // handle errors during logging.
// default handler prints the error to stderr at max rate of 1 message/sec. // default handler prints the error to stderr at max rate of 1 message/sec.
void err_handler_(const std::string &msg); void err_handler_(const std::string &msg) const;
}; };
void swap(logger &a, logger &b); void swap(logger &a, logger &b) noexcept;
} // namespace spdlog } // namespace spdlog

View File

@@ -12,12 +12,14 @@
#include <spdlog/common.h> #include <spdlog/common.h>
// MDC is a simple map of key->string values stored in thread local storage whose content will be printed by the loggers. // MDC is a simple map of key->string values stored in thread local storage whose content will be
// Note: Not supported in async mode (thread local storage - so the async thread pool have different copy). // printed by the loggers. Note: Not supported in async mode (thread local storage - so the async
// thread pool have different copy).
// //
// Usage example: // Usage example:
// spdlog::mdc::put("mdc_key_1", "mdc_value_1"); // spdlog::mdc::put("mdc_key_1", "mdc_value_1");
// spdlog::info("Hello, {}", "World!"); // => [2024-04-26 02:08:05.040] [info] [mdc_key_1:mdc_value_1] Hello, World! // spdlog::info("Hello, {}", "World!"); // => [2024-04-26 02:08:05.040] [info]
// [mdc_key_1:mdc_value_1] Hello, World!
namespace spdlog { namespace spdlog {
class SPDLOG_API mdc { class SPDLOG_API mdc {

View File

@@ -70,6 +70,9 @@ public:
pad_it(remaining_pad_); pad_it(remaining_pad_);
} else if (padinfo_.truncate_) { } else if (padinfo_.truncate_) {
long new_size = static_cast<long>(dest_.size()) + remaining_pad_; long new_size = static_cast<long>(dest_.size()) + remaining_pad_;
if (new_size < 0) {
new_size = 0;
}
dest_.resize(static_cast<size_t>(new_size)); dest_.resize(static_cast<size_t>(new_size));
} }
} }
@@ -264,7 +267,7 @@ public:
: flag_formatter(padinfo) {} : flag_formatter(padinfo) {}
void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override {
const size_t field_size = 10; const size_t field_size = 8;
ScopedPadder p(field_size, padinfo_, dest); ScopedPadder p(field_size, padinfo_, dest);
fmt_helper::pad2(tm_time.tm_mon + 1, dest); fmt_helper::pad2(tm_time.tm_mon + 1, dest);
@@ -926,9 +929,8 @@ private:
memory_buf_t cached_datetime_; memory_buf_t cached_datetime_;
#ifndef SPDLOG_NO_TLS #ifndef SPDLOG_NO_TLS
mdc_formatter<null_scoped_padder> mdc_formatter_{padding_info{}}; mdc_formatter<null_scoped_padder> mdc_formatter_{padding_info {}};
#endif #endif
}; };
} // namespace details } // namespace details

View File

@@ -111,7 +111,8 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode_(color_mode mode
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) const { SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(
const string_view_t &color_code) const {
details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_); details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_);
} }

View File

@@ -40,7 +40,7 @@ public:
void log(const details::log_msg &msg) override; void log(const details::log_msg &msg) override;
void flush() override; void flush() override;
void set_pattern(const std::string &pattern) final override; void set_pattern(const std::string &pattern) override;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override;
// Formatting codes // Formatting codes

View File

@@ -27,7 +27,7 @@ public:
protected: protected:
void sink_it_(const details::log_msg &msg) override { callback_(msg); } void sink_it_(const details::log_msg &msg) override { callback_(msg); }
void flush_() override{} void flush_() override {}
private: private:
custom_log_callback callback_; custom_log_callback callback_;

View File

@@ -40,22 +40,21 @@ template <typename Mutex>
class dup_filter_sink : public dist_sink<Mutex> { class dup_filter_sink : public dist_sink<Mutex> {
public: public:
template <class Rep, class Period> template <class Rep, class Period>
explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration, explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration)
level::level_enum notification_level = level::info) : max_skip_duration_{max_skip_duration} {}
: max_skip_duration_{max_skip_duration},
log_level_{notification_level} {}
protected: protected:
std::chrono::microseconds max_skip_duration_; std::chrono::microseconds max_skip_duration_;
log_clock::time_point last_msg_time_; log_clock::time_point last_msg_time_;
std::string last_msg_payload_; std::string last_msg_payload_;
size_t skip_counter_ = 0; size_t skip_counter_ = 0;
level::level_enum log_level_; level::level_enum skipped_msg_log_level_ = spdlog::level::level_enum::off;
void sink_it_(const details::log_msg &msg) override { void sink_it_(const details::log_msg &msg) override {
bool filtered = filter_(msg); bool filtered = filter_(msg);
if (!filtered) { if (!filtered) {
skip_counter_ += 1; skip_counter_ += 1;
skipped_msg_log_level_ = msg.level;
return; return;
} }
@@ -65,7 +64,7 @@ protected:
auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..",
static_cast<unsigned>(skip_counter_)); static_cast<unsigned>(skip_counter_));
if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf)) { if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf)) {
details::log_msg skipped_msg{msg.source, msg.logger_name, log_level_, details::log_msg skipped_msg{msg.source, msg.logger_name, skipped_msg_log_level_,
string_view_t{buf, static_cast<size_t>(msg_size)}}; string_view_t{buf, static_cast<size_t>(msg_size)}};
dist_sink<Mutex>::sink_it_(skipped_msg); dist_sink<Mutex>::sink_it_(skipped_msg);
} }

View File

@@ -21,7 +21,11 @@ template <typename Mutex>
class ringbuffer_sink final : public base_sink<Mutex> { class ringbuffer_sink final : public base_sink<Mutex> {
public: public:
explicit ringbuffer_sink(size_t n_items) explicit ringbuffer_sink(size_t n_items)
: q_{n_items} {} : q_{n_items} {
if (n_items == 0) {
throw_spdlog_ex("ringbuffer_sink: n_items cannot be zero");
}
}
std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) { std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);

View File

@@ -14,7 +14,6 @@
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
#include <cerrno> #include <cerrno>
#include <chrono>
#include <ctime> #include <ctime>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -38,8 +37,8 @@ SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink(
throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero");
} }
if (max_files > 200000) { if (max_files > MaxFiles) {
throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000"); throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed MaxFiles");
} }
file_helper_.open(calc_filename(base_filename_, 0)); file_helper_.open(calc_filename(base_filename_, 0));
current_size_ = file_helper_.size(); // expensive. called only once current_size_ = file_helper_.size(); // expensive. called only once
@@ -54,11 +53,12 @@ SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink(
template <typename Mutex> template <typename Mutex>
SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename,
std::size_t index) { std::size_t index) {
if (index == 0u) { if (index == 0U) {
return filename; return filename;
} }
filename_t basename, ext; filename_t basename;
filename_t ext;
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
return fmt_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}.{}{}")), basename, index, ext); return fmt_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}.{}{}")), basename, index, ext);
} }
@@ -74,6 +74,35 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_now() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
rotate_(); rotate_();
} }
template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::set_max_size(std::size_t max_size) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
if (max_size == 0) {
throw_spdlog_ex("rotating sink set_max_size: max_size arg cannot be zero");
}
max_size_ = max_size;
}
template <typename Mutex>
SPDLOG_INLINE std::size_t rotating_file_sink<Mutex>::get_max_size() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return max_size_;
}
template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::set_max_files(std::size_t max_files) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
if (max_files > MaxFiles) {
throw_spdlog_ex("rotating sink set_max_files: max_files arg cannot exceed 200000");
}
max_files_ = max_files;
}
template <typename Mutex>
std::size_t rotating_file_sink<Mutex>::get_max_files() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return max_files_;
}
template <typename Mutex> template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) { SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) {

View File

@@ -8,7 +8,6 @@
#include <spdlog/details/synchronous_factory.h> #include <spdlog/details/synchronous_factory.h>
#include <spdlog/sinks/base_sink.h> #include <spdlog/sinks/base_sink.h>
#include <chrono>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -21,6 +20,7 @@ namespace sinks {
template <typename Mutex> template <typename Mutex>
class rotating_file_sink final : public base_sink<Mutex> { class rotating_file_sink final : public base_sink<Mutex> {
public: public:
static constexpr size_t MaxFiles = 200000;
rotating_file_sink(filename_t base_filename, rotating_file_sink(filename_t base_filename,
std::size_t max_size, std::size_t max_size,
std::size_t max_files, std::size_t max_files,
@@ -29,6 +29,10 @@ public:
static filename_t calc_filename(const filename_t &filename, std::size_t index); static filename_t calc_filename(const filename_t &filename, std::size_t index);
filename_t filename(); filename_t filename();
void rotate_now(); void rotate_now();
void set_max_size(std::size_t max_size);
std::size_t get_max_size();
void set_max_files(std::size_t max_files);
std::size_t get_max_files();
protected: protected:
void sink_it_(const details::log_msg &msg) override; void sink_it_(const details::log_msg &msg) override;
@@ -42,7 +46,7 @@ private:
// log.3.txt -> delete // log.3.txt -> delete
void rotate_(); void rotate_();
// delete the target if exists, and rename the src file to target // delete the target if exists, and rename the src file to target
// return true on success, false otherwise. // return true on success, false otherwise.
bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); bool rename_file_(const filename_t &src_filename, const filename_t &target_filename);
@@ -61,25 +65,24 @@ using rotating_file_sink_st = rotating_file_sink<details::null_mutex>;
// //
// factory functions // factory functions
// //
template <typename Factory = spdlog::synchronous_factory> template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name, std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name,
const filename_t &filename, const filename_t &filename,
size_t max_file_size, size_t max_file_size,
size_t max_files, size_t max_files,
bool rotate_on_open = false, bool rotate_on_open = false,
const file_event_handlers &event_handlers = {}) { const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::rotating_file_sink_mt>( return Factory::template create<sinks::rotating_file_sink_mt>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
} }
template <typename Factory = spdlog::synchronous_factory> template <typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name, std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name,
const filename_t &filename, const filename_t &filename,
size_t max_file_size, size_t max_file_size,
size_t max_files, size_t max_files,
bool rotate_on_open = false, bool rotate_on_open = false,
const file_event_handlers &event_handlers = {}) { const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::rotating_file_sink_st>( return Factory::template create<sinks::rotating_file_sink_st>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
} }

View File

@@ -31,6 +31,8 @@ namespace sinks {
struct tcp_sink_config { struct tcp_sink_config {
std::string server_host; std::string server_host;
int server_port; int server_port;
int timeout_ms =
0; // The timeout for all 3 major socket operations that is connect, send, and recv
bool lazy_connect = false; // if true connect on first log call instead of on construction bool lazy_connect = false; // if true connect on first log call instead of on construction
tcp_sink_config(std::string host, int port) tcp_sink_config(std::string host, int port)
@@ -44,10 +46,22 @@ public:
// connect to tcp host/port or throw if failed // connect to tcp host/port or throw if failed
// host can be hostname or ip address // host can be hostname or ip address
explicit tcp_sink(const std::string &host,
int port,
int timeout_ms = 0,
bool lazy_connect = false)
: config_{host, port} {
config_.timeout_ms = timeout_ms;
config_.lazy_connect = lazy_connect;
if (!config_.lazy_connect) {
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
}
}
explicit tcp_sink(tcp_sink_config sink_config) explicit tcp_sink(tcp_sink_config sink_config)
: config_{std::move(sink_config)} { : config_{std::move(sink_config)} {
if (!config_.lazy_connect) { if (!config_.lazy_connect) {
this->client_.connect(config_.server_host, config_.server_port); client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
} }
} }
@@ -58,7 +72,7 @@ protected:
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
if (!client_.is_connected()) { if (!client_.is_connected()) {
client_.connect(config_.server_host, config_.server_port); client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
} }
client_.send(formatted.data(), formatted.size()); client_.send(formatted.data(), formatted.size());
} }

View File

@@ -137,10 +137,10 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t
#if defined(SPDLOG_UTF8_TO_WCHAR_CONSOLE) #if defined(SPDLOG_UTF8_TO_WCHAR_CONSOLE)
wmemory_buf_t wformatted; wmemory_buf_t wformatted;
details::os::utf8_to_wstrbuf(string_view_t(formatted.data() + start, end - start), details::os::utf8_to_wstrbuf(string_view_t(formatted.data() + start, end - start),
wformatted); wformatted);
auto size = static_cast<DWORD>(wformatted.size()); auto size = static_cast<DWORD>(wformatted.size());
auto ignored = ::WriteConsoleW(static_cast<HANDLE>(out_handle_), wformatted.data(), size, auto ignored = ::WriteConsoleW(static_cast<HANDLE>(out_handle_), wformatted.data(), size,
nullptr, nullptr); nullptr, nullptr);
#else #else
auto size = static_cast<DWORD>(end - start); auto size = static_cast<DWORD>(end - start);
auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start, auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start,

View File

@@ -31,10 +31,10 @@ public:
// change the color for the given level // change the color for the given level
void set_color(level::level_enum level, std::uint16_t color); void set_color(level::level_enum level, std::uint16_t color);
void log(const details::log_msg &msg) final override; void log(const details::log_msg &msg) override;
void flush() final override; void flush() override;
void set_pattern(const std::string &pattern) override final; void set_pattern(const std::string &pattern) override;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final; void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override;
void set_color_mode(color_mode mode); void set_color_mode(color_mode mode);
protected: protected:

View File

@@ -59,6 +59,10 @@ SPDLOG_INLINE void register_logger(std::shared_ptr<logger> logger) {
details::registry::instance().register_logger(std::move(logger)); details::registry::instance().register_logger(std::move(logger));
} }
SPDLOG_INLINE void register_or_replace(std::shared_ptr<logger> logger) {
details::registry::instance().register_or_replace(std::move(logger));
}
SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) { SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) {
details::registry::instance().apply_all(fun); details::registry::instance().apply_all(fun);
} }

View File

@@ -25,7 +25,7 @@ namespace spdlog {
using default_factory = synchronous_factory; using default_factory = synchronous_factory;
// Create and register a logger with a templated sink type // Create and register a logger with a templated sink type
// The logger's level, formatter and flush level will be set according the // The logger's level, formatter and flush level will be set according to the
// global settings. // global settings.
// //
// Example: // Example:
@@ -46,7 +46,7 @@ inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs
// spdlog::initialize_logger(mylogger); // spdlog::initialize_logger(mylogger);
SPDLOG_API void initialize_logger(std::shared_ptr<logger> logger); SPDLOG_API void initialize_logger(std::shared_ptr<logger> logger);
// Return an existing logger or nullptr if a logger with such name doesn't // Return an existing logger or nullptr if a logger with such a name doesn't
// exist. // exist.
// example: spdlog::get("my_logger")->info("hello {}", "world"); // example: spdlog::get("my_logger")->info("hello {}", "world");
SPDLOG_API std::shared_ptr<logger> get(const std::string &name); SPDLOG_API std::shared_ptr<logger> get(const std::string &name);
@@ -71,13 +71,13 @@ SPDLOG_API void dump_backtrace();
// Get global logging level // Get global logging level
SPDLOG_API level::level_enum get_level(); SPDLOG_API level::level_enum get_level();
// Set global logging level // Set the global logging level
SPDLOG_API void set_level(level::level_enum log_level); SPDLOG_API void set_level(level::level_enum log_level);
// Determine whether the default logger should log messages with a certain level // Determine whether the default logger should log messages with a certain level
SPDLOG_API bool should_log(level::level_enum lvl); SPDLOG_API bool should_log(level::level_enum lvl);
// Set global flush level // Set a global flush level
SPDLOG_API void flush_on(level::level_enum log_level); SPDLOG_API void flush_on(level::level_enum log_level);
// Start/Restart a periodic flusher thread // Start/Restart a periodic flusher thread
@@ -91,9 +91,14 @@ inline void flush_every(std::chrono::duration<Rep, Period> interval) {
SPDLOG_API void set_error_handler(void (*handler)(const std::string &msg)); SPDLOG_API void set_error_handler(void (*handler)(const std::string &msg));
// Register the given logger with the given name // Register the given logger with the given name
// Will throw if a logger with the same name already exists.
SPDLOG_API void register_logger(std::shared_ptr<logger> logger); SPDLOG_API void register_logger(std::shared_ptr<logger> logger);
// Apply a user defined function on all registered loggers // Register the given logger with the given name
// Will replace any existing logger with the same name.
SPDLOG_API void register_or_replace(std::shared_ptr<logger> logger);
// Apply a user-defined function on all registered loggers
// Example: // Example:
// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); // spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();});
SPDLOG_API void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun); SPDLOG_API void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun);
@@ -111,19 +116,19 @@ SPDLOG_API void shutdown();
SPDLOG_API void set_automatic_registration(bool automatic_registration); SPDLOG_API void set_automatic_registration(bool automatic_registration);
// API for using default logger (stdout_color_mt), // API for using default logger (stdout_color_mt),
// e.g: spdlog::info("Message {}", 1); // e.g.: spdlog::info("Message {}", 1);
// //
// The default logger object can be accessed using the spdlog::default_logger(): // The default logger object can be accessed using the spdlog::default_logger():
// For example, to add another sink to it: // For example, to add another sink to it:
// spdlog::default_logger()->sinks().push_back(some_sink); // spdlog::default_logger()->sinks().push_back(some_sink);
// //
// The default logger can replaced using spdlog::set_default_logger(new_logger). // The default logger can be replaced using spdlog::set_default_logger(new_logger).
// For example, to replace it with a file logger. // For example, to replace it with a file logger.
// //
// IMPORTANT: // IMPORTANT:
// The default API is thread safe (for _mt loggers), but: // The default API is thread safe (for _mt loggers), but:
// set_default_logger() *should not* be used concurrently with the default API. // set_default_logger() *should not* be used concurrently with the default API.
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. // e.g., do not call set_default_logger() from one thread while calling spdlog::info() from another.
SPDLOG_API std::shared_ptr<spdlog::logger> default_logger(); SPDLOG_API std::shared_ptr<spdlog::logger> default_logger();

View File

@@ -109,8 +109,8 @@
// //
// #include <string_view> // #include <string_view>
// using namespace std::string_view_literals; // using namespace std::string_view_literals;
// #define SPDLOG_LEVEL_NAMES { "MY TRACE"sv, "MY DEBUG"sv, "MY INFO"sv, "MY WARNING"sv, "MY ERROR"sv, "MY // #define SPDLOG_LEVEL_NAMES { "MY TRACE"sv, "MY DEBUG"sv, "MY INFO"sv, "MY WARNING"sv, "MY
// CRITICAL"sv, "OFF"sv } // ERROR"sv, "MY CRITICAL"sv, "OFF"sv }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@@ -4,8 +4,8 @@
#pragma once #pragma once
#define SPDLOG_VER_MAJOR 1 #define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 15 #define SPDLOG_VER_MINOR 16
#define SPDLOG_VER_PATCH 1 #define SPDLOG_VER_PATCH 0
#define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch) #define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch)
#define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH) #define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH)

View File

@@ -11,6 +11,12 @@
#include <spdlog/fmt/bundled/format-inl.h> #include <spdlog/fmt/bundled/format-inl.h>
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
namespace detail { namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept template FMT_API auto dragonbox::to_decimal(float x) noexcept
@@ -18,12 +24,6 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>; -> dragonbox::decimal_fp<double>;
#if FMT_USE_LOCALE
// DEPRECATED! locale_ref in the detail namespace
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref) template FMT_API auto thousands_sep_impl(locale_ref)
@@ -33,20 +33,16 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED! // DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref) template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>; -> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
// DEPRECATED!
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*); template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail } // namespace detail
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // !SPDLOG_FMT_EXTERNAL #endif // !SPDLOG_FMT_EXTERNAL

View File

@@ -19,9 +19,9 @@ if(Catch2_FOUND)
else() else()
message(STATUS "Bundled version of Catch will be downloaded and used.") message(STATUS "Bundled version of Catch will be downloaded and used.")
include(FetchContent) include(FetchContent)
FetchContent_Declare(Catch2 FetchContent_Declare(
GIT_REPOSITORY https://github.com/catchorg/Catch2.git Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0 GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0
) )
FetchContent_MakeAvailable(Catch2) FetchContent_MakeAvailable(Catch2)
endif() endif()
@@ -49,7 +49,8 @@ set(SPDLOG_UTESTS_SOURCES
test_time_point.cpp test_time_point.cpp
test_stopwatch.cpp test_stopwatch.cpp
test_circular_q.cpp test_circular_q.cpp
test_bin_to_hex.cpp) test_bin_to_hex.cpp
test_ringbuffer.cpp)
if(NOT SPDLOG_NO_EXCEPTIONS) if(NOT SPDLOG_NO_EXCEPTIONS)
list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp) list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp)
@@ -71,10 +72,10 @@ function(spdlog_prepare_test test_target spdlog_lib)
target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain) target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain)
if(SPDLOG_SANITIZE_ADDRESS) if(SPDLOG_SANITIZE_ADDRESS)
spdlog_enable_addr_sanitizer(${test_target}) spdlog_enable_addr_sanitizer(${test_target})
elseif (SPDLOG_SANITIZE_THREAD) elseif(SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(${test_target}) spdlog_enable_thread_sanitizer(${test_target})
endif () endif()
add_test(NAME ${test_target} COMMAND ${test_target}) add_test(NAME ${test_target} COMMAND ${test_target} --order decl)
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON) set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction() endfunction()

View File

@@ -16,7 +16,8 @@ TEST_CASE("custom_callback_logger", "[custom_callback_logger]") {
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
formatter.format(msg, formatted); formatter.format(msg, formatted);
auto eol_len = strlen(spdlog::details::os::default_eol); auto eol_len = strlen(spdlog::details::os::default_eol);
using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type; using diff_t =
typename std::iterator_traits<decltype(formatted.end())>::difference_type;
lines.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); lines.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len));
}); });
std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st); std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st);

View File

@@ -46,10 +46,8 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]") {
struct custom_daily_file_name_calculator { struct custom_daily_file_name_calculator {
static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) { static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) {
return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename,
return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
now_tm.tm_mday);
} }
}; };

View File

@@ -56,7 +56,7 @@ TEST_CASE("simple_file_logger", "[truncate]") {
logger->info("Test message {}", 2.71); logger->info("Test message {}", 2.71);
logger->flush(); logger->flush();
REQUIRE(count_lines(SIMPLE_LOG) == 2); REQUIRE(count_lines(SIMPLE_LOG) == 2);
sink->truncate(); sink->truncate();
REQUIRE(count_lines(SIMPLE_LOG) == 0); REQUIRE(count_lines(SIMPLE_LOG) == 0);
@@ -94,14 +94,11 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]") {
// next logger can rename the first output file. // next logger can rename the first output file.
spdlog::drop(logger->name()); spdlog::drop(logger->name());
} }
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true);
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
logger->info("Test message {}", i); logger->info("Test message {}", i);
} }
logger->flush(); logger->flush();
require_message_count(ROTATING_LOG, 10); require_message_count(ROTATING_LOG, 10);
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@@ -141,3 +138,50 @@ TEST_CASE("rotating_file_logger4", "[rotating_logger]") {
REQUIRE(get_filesize(ROTATING_LOG) > 0); REQUIRE(get_filesize(ROTATING_LOG) > 0);
REQUIRE(get_filesize(ROTATING_LOG ".1") > 0); REQUIRE(get_filesize(ROTATING_LOG ".1") > 0);
} }
// test changing the max size of the rotating file sink
TEST_CASE("rotating_file_logger5", "[rotating_logger]") {
prepare_logdir();
size_t max_size = 5 * 1024;
size_t max_files = 2;
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
auto sink =
std::make_shared<spdlog::sinks::rotating_file_sink_st>(basename, max_size, max_files);
auto logger = std::make_shared<spdlog::logger>("rotating_sink_logger", sink);
logger->set_pattern("%v");
REQUIRE(sink->get_max_size() == max_size);
REQUIRE(sink->get_max_files() == max_files);
max_size = 7 * 1024;
max_files = 3;
sink->set_max_size(max_size);
sink->set_max_files(max_files);
REQUIRE(sink->get_max_size() == max_size);
REQUIRE(sink->get_max_files() == max_files);
const auto message = std::string(200, 'x');
assert(message.size() < max_size);
const auto n_messages = max_files * max_size / message.size();
for (size_t i = 0; i < n_messages; ++i) {
logger->info(message);
}
logger.reset(); // force flush and close the file
// validate that the files were rotated correctly with the new max size and max files
for (size_t i = 0; i <= max_files; i++) {
// calc filenames
// e.g. rotating_log, rotating_log.0 rotating_log.1, rotating_log.2, etc.
std::ostringstream oss;
oss << ROTATING_LOG;
if (i > 0) {
oss << '.' << i;
}
const auto filename = oss.str();
const auto filesize = get_filesize(filename);
REQUIRE(filesize <= max_size);
if (i > 0) {
REQUIRE(filesize >= max_size - message.size() - 2);
}
}
}

View File

@@ -1,12 +1,12 @@
#ifdef _WIN32 // to prevent fopen warning on windows #ifdef _WIN32 // to prevent fopen warning on windows
#define _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS
#endif #endif
#include "includes.h" #include "includes.h"
#include "test_sink.h" #include "test_sink.h"
template <class T> template <class T>
std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info) { std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info) {
std::ostringstream oss; std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
@@ -182,17 +182,19 @@ TEST_CASE("utf8 to utf16 conversion using windows api", "[windows utf]") {
spdlog::details::os::utf8_to_wstrbuf("abc", buffer); spdlog::details::os::utf8_to_wstrbuf("abc", buffer);
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc"));
spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence. spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence.
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd(")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd("));
spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93", buffer); // "Neko" in hiragana. spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93",
buffer); // "Neko" in hiragana.
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053"));
} }
#endif #endif
struct auto_closer { struct auto_closer {
FILE* fp = nullptr; FILE* fp = nullptr;
explicit auto_closer(FILE* f) : fp(f) {} explicit auto_closer(FILE* f)
: fp(f) {}
auto_closer(const auto_closer&) = delete; auto_closer(const auto_closer&) = delete;
auto_closer& operator=(const auto_closer&) = delete; auto_closer& operator=(const auto_closer&) = delete;
~auto_closer() { ~auto_closer() {
@@ -201,10 +203,10 @@ struct auto_closer {
}; };
TEST_CASE("os::fwrite_bytes", "[os]") { TEST_CASE("os::fwrite_bytes", "[os]") {
using spdlog::details::os::fwrite_bytes;
using spdlog::details::os::create_dir; using spdlog::details::os::create_dir;
using spdlog::details::os::fwrite_bytes;
const char* filename = "log_tests/test_fwrite_bytes.txt"; const char* filename = "log_tests/test_fwrite_bytes.txt";
const char *msg = "hello"; const char* msg = "hello";
prepare_logdir(); prepare_logdir();
REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true); REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true);
{ {

View File

@@ -1,6 +1,8 @@
#include "includes.h" #include "includes.h"
#include "test_sink.h" #include "test_sink.h"
#include <chrono>
using spdlog::memory_buf_t; using spdlog::memory_buf_t;
using spdlog::details::to_string_view; using spdlog::details::to_string_view;
@@ -19,6 +21,23 @@ static std::string log_to_str(const std::string &msg, const Args &...args) {
return oss.str(); return oss.str();
} }
// log to str and return it with time
template <typename... Args>
static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time,
const std::string &msg,
const Args &...args) {
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("pattern_tester", oss_sink);
oss_logger.set_level(spdlog::level::info);
oss_logger.set_formatter(
std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(args...)));
oss_logger.log(log_time, {}, spdlog::level::info, msg);
return oss.str();
}
TEST_CASE("custom eol", "[pattern_formatter]") { TEST_CASE("custom eol", "[pattern_formatter]") {
std::string msg = "Hello custom eol test"; std::string msg = "Hello custom eol test";
std::string eol = ";)"; std::string eol = ";)";
@@ -58,6 +77,15 @@ TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") {
oss.str()); oss.str());
} }
TEST_CASE("GMT offset ", "[pattern_formatter]") {
using namespace std::chrono_literals;
const auto now = std::chrono::system_clock::now();
const auto yesterday = now - 24h;
REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc,
"\n") == "+00:00\n");
}
TEST_CASE("color range test1", "[pattern_formatter]") { TEST_CASE("color range test1", "[pattern_formatter]") {
auto formatter = std::make_shared<spdlog::pattern_formatter>( auto formatter = std::make_shared<spdlog::pattern_formatter>(
"%^%v%$", spdlog::pattern_time_type::local, "\n"); "%^%v%$", spdlog::pattern_time_type::local, "\n");

View File

@@ -25,6 +25,19 @@ TEST_CASE("explicit register", "[registry]") {
} }
#endif #endif
TEST_CASE("register_or_replace", "[registry]") {
spdlog::drop_all();
auto logger1 = std::make_shared<spdlog::logger>(
tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
spdlog::register_logger(logger1);
REQUIRE(spdlog::get(tested_logger_name) == logger1);
auto logger2 = std::make_shared<spdlog::logger>(
tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
spdlog::register_or_replace(logger2);
REQUIRE(spdlog::get(tested_logger_name) == logger2);
}
TEST_CASE("apply_all", "[registry]") { TEST_CASE("apply_all", "[registry]") {
spdlog::drop_all(); spdlog::drop_all();
auto logger = std::make_shared<spdlog::logger>(tested_logger_name, auto logger = std::make_shared<spdlog::logger>(tested_logger_name,

52
tests/test_ringbuffer.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "includes.h"
#include "spdlog/sinks/ringbuffer_sink.h"
TEST_CASE("ringbuffer invalid size", "[ringbuffer]") {
REQUIRE_THROWS_AS(spdlog::sinks::ringbuffer_sink_mt(0), spdlog::spdlog_ex);
}
TEST_CASE("ringbuffer stores formatted messages", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg1"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg2"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg3"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 3);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("msg1{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("msg2{}", default_eol));
REQUIRE(formatted[2] == spdlog::fmt_lib::format("msg3{}", default_eol));
}
TEST_CASE("ringbuffer overrun keeps last items", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(2);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "first"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "second"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "third"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("second{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("third{}", default_eol));
}
TEST_CASE("ringbuffer retrieval limit", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "A"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "B"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "C"});
auto formatted = sink.last_formatted(2);
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("B{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("C{}", default_eol));
}

View File

@@ -47,7 +47,7 @@ protected:
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
// save the line without the eol // save the line without the eol
auto eol_len = strlen(details::os::default_eol); auto eol_len = strlen(details::os::default_eol);
using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type; using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type;
if (lines_.size() < lines_to_save) { if (lines_.size() < lines_to_save) {
lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len));
} }

View File

@@ -5,7 +5,7 @@
TEST_CASE("stopwatch1", "[stopwatch]") { TEST_CASE("stopwatch1", "[stopwatch]") {
using std::chrono::milliseconds; using std::chrono::milliseconds;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
milliseconds wait_ms(200); milliseconds wait_ms(500);
milliseconds tolerance_ms(250); milliseconds tolerance_ms(250);
auto start = clock::now(); auto start = clock::now();
spdlog::stopwatch sw; spdlog::stopwatch sw;
@@ -22,7 +22,7 @@ TEST_CASE("stopwatch2", "[stopwatch]") {
using std::chrono::milliseconds; using std::chrono::milliseconds;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
clock::duration wait_duration(milliseconds(200)); clock::duration wait_duration(milliseconds(500));
clock::duration tolerance_duration(milliseconds(250)); clock::duration tolerance_duration(milliseconds(250));
auto test_sink = std::make_shared<test_sink_st>(); auto test_sink = std::make_shared<test_sink_st>();

View File

@@ -50,9 +50,8 @@ void require_message_count(const std::string &filename, const std::size_t messag
std::size_t get_filesize(const std::string &filename) { std::size_t get_filesize(const std::string &filename) {
std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary);
if (!ifs) { if (!ifs) {
throw std::runtime_error("Failed open file "); throw std::runtime_error("Failed open file " + filename);
} }
return static_cast<std::size_t>(ifs.tellg()); return static_cast<std::size_t>(ifs.tellg());
} }