Compare commits

..

52 Commits

Author SHA1 Message Date
Gabi Melman
0d5d8140ec Expand ringbuffer sink tests 2025-07-06 00:23:27 +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
Gabi Melman
f355b3d58f Fix test_daily_logger 2025-02-01 13:08:54 +02:00
Gabi Melman
ac432c3602 Gabime/v1.15.1 (#3332)
* Updated bundled fmt to version 11.1.3

* Bump version to 1.15.1
2025-02-01 12:09:54 +02:00
Janusz Chorko
3c23c27d2d Revert "fix: Compatibility with external fmtlib 11.1.1 (#3312)" (#3331)
This reverts commit 7f8060d5b2.
2025-02-01 11:15:04 +02:00
Ken Matsui
ae1de0dc8c Support custom environment variables for load_env_levels (#3327)
SPDLOG_LEVEL is currently supported to control log levels via
load_env_levels.

This patch adds support for other environment variable names, such as
MYAPP_LEVEL, for load_env_levels.
2025-01-23 23:00:23 +02:00
Gabi Melman
7cbf2a6967 Gabime/ansicolor sink improvements (#3323)
* Added lock to set_color_mode in asnicolor_sink

* Added const qualifiers to some ansicolor_sink functions
2025-01-18 11:58:32 +02:00
Alexander
57505989b7 SPDLOG_LEVEL_NAMES, comment use string_view_literals (#3291)
* SPDLOG_LEVEL_NAMES, comment use string_view_literals

* SPDLOG_LEVEL_NAMES, comment use string_view_literals
2025-01-18 11:57:43 +02:00
gabime
96a7d2a1d4 Format CMakeLists.txt 2025-01-12 08:37:47 +02:00
Gabi Melman
d71555306a Added SPDLOG_FWRITE_UNLOCKED option to CMakeLists.txt (#3318)
* Added SPDLOG_FWRITE_UNLOCKED option to CMakeLists.txt

* Update CMakeLists.txt
2025-01-12 08:02:39 +02:00
koniarik
ad0f31c009 Enabled bin_to_hex utest for stdformat, fixed std::formatter (#3315)
* Enabled bin_to_hex utest for stdformat, and fixed std::formatter

* fixed usage of \ in macos.yml

* explicitly cast diff variable in test_sink

* moved from ::iterator to decltype

* added fix for custom callbacks

---------

Co-authored-by: Jan Koniarik <veverak@Jans-MacBook-Pro.local>
2025-01-11 17:21:39 +02:00
jdrouhard
96a8f6250c fix: remove unused to_string_view overload in fmt >= 11.1 (#3314) 2025-01-10 00:58:46 +02:00
Christian Blichmann
7f8060d5b2 fix: Compatibility with external fmtlib 11.1.1 (#3312)
Include fmtlib's `xchar` header to include `fmt::basic_format_string`.
Otherwise, compilation with an external fmtlib 11.1.1 fails with

```
In file included from include/spdlog/spdlog.h:12:
include/spdlog/common.h:369:49: error: no template named 'basic_format_string' in namespace 'fmt'; did you mean 'std::basic_format_string'?
  369 | inline fmt::basic_string_view<T> to_string_view(fmt::basic_format_string<T, Args...> fmt) {
      |                                                 ^~~~~
```

Signed-off-by: Christian Blichmann <cblichmann@google.com>
2025-01-08 00:59:12 +02:00
Rui Chen
276ee5f5c0 fix: update to_string_view function for fmt 11.1 (#3301)
Signed-off-by: Rui Chen <rui@chenrui.dev>
2024-12-26 09:13:57 +02:00
Matteo Del Seppia
24dde318fe Adding lock to rotate_now() (#3281) 2024-12-03 09:53:34 +02:00
Matteo Del Seppia
65e388e82b Adding on demand truncation for basic file sinks (#3280)
* Adding support to truncate on demand for basic file sink

* Remove unnecessary file close

* Adding lock in basic_file_sink truncate()
2024-12-03 01:38:51 +02:00
Gabi Melman
1e6250e183 Gabime/fwrite unlocked (#3276)
* Use locking fwrite_unlocked if possible

* Added compile definitions to header_only
2024-12-01 14:16:52 +02:00
hjs-ast
951c5b9987 Allow manual rotation of rotating_file_sink (#3269)
* Allow manual rotation of rotating_file_sink

* Rename rotation method

* Attempted fix for tests on Windows

* Apply review mark-ups
2024-11-28 17:37:29 +02:00
Gabi Melman
15f539685b Update null_sink to be final (#3267) 2024-11-25 00:32:47 +02:00
Gabi Melman
43dcb3982d Update CMakeLists.txt comment 2024-11-22 12:14:47 +02:00
Gabi Melman
0efef2af24 Update CMakeLists.txt comment 2024-11-22 12:12:32 +02:00
Gabi Melman
018d8aa266 Update CMakeLists.txt 2024-11-22 12:10:53 +02:00
Gabi Melman
35b0417fbe Update CMakeLists.txt comment 2024-11-22 12:09:48 +02:00
Gabi Melman
94526fa8e8 Update CMakeLists.txt comment 2024-11-22 12:07:39 +02:00
Gabi Melman
633003f40a Update CMakeLists.txt comment 2024-11-22 11:50:05 +02:00
miyanyan
9edab1b5a1 pass /utf-8 only when compiler is MSVC (#3260) 2024-11-22 11:42:35 +02:00
LiAuTraver
1245bf8e8a add explicit mt:: and std:: to avoid ambiguous call when both std::format_to and mt::format_to are present (#3259) 2024-11-18 16:41:27 +02:00
F1F88
51a0deca2c docs: Removed duplicate line in daily_file_sink comment (#3249) 2024-11-09 21:41:00 +02:00
69 changed files with 4728 additions and 4697 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

@@ -9,6 +9,15 @@ jobs:
build: build:
runs-on: macOS-latest runs-on: macOS-latest
name: "macOS Clang (C++11, Release)" name: "macOS Clang (C++11, Release)"
strategy:
fail-fast: true
matrix:
config:
- USE_STD_FORMAT: 'ON'
BUILD_EXAMPLE: 'OFF'
- USE_STD_FORMAT: 'OFF'
BUILD_EXAMPLE: 'ON'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build - name: Build
@@ -17,12 +26,13 @@ jobs:
cmake .. \ cmake .. \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=11 \ -DCMAKE_CXX_STANDARD=11 \
-DSPDLOG_BUILD_EXAMPLE=ON \ -DSPDLOG_BUILD_EXAMPLE=${{ matrix.config.BUILD_EXAMPLE }} \
-DSPDLOG_BUILD_EXAMPLE_HO=ON \ -DSPDLOG_BUILD_EXAMPLE_HO=${{ matrix.config.BUILD_EXAMPLE }} \
-DSPDLOG_BUILD_WARNINGS=ON \ -DSPDLOG_BUILD_WARNINGS=ON \
-DSPDLOG_BUILD_BENCH=OFF \ -DSPDLOG_BUILD_BENCH=OFF \
-DSPDLOG_BUILD_TESTS=ON \ -DSPDLOG_BUILD_TESTS=ON \
-DSPDLOG_BUILD_TESTS_HO=OFF \ -DSPDLOG_BUILD_TESTS_HO=OFF \
-DSPDLOG_USE_STD_FORMAT=${{ matrix.config.USE_STD_FORMAT }} \
-DSPDLOG_SANITIZE_ADDRESS=OFF -DSPDLOG_SANITIZE_ADDRESS=OFF
make -j 4 make -j 4
ctest -j 4 --output-on-failure ctest -j 4 --output-on-failure

View File

@@ -33,7 +33,6 @@ elseif(NOT CMAKE_CXX_STANDARD)
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")
@@ -108,15 +107,15 @@ endif()
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")
@@ -133,6 +132,7 @@ option(
"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)
# clang-tidy # clang-tidy
option(SPDLOG_TIDY "run clang-tidy" OFF) option(SPDLOG_TIDY "run clang-tidy" OFF)
@@ -201,9 +201,9 @@ 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
@@ -238,6 +238,22 @@ if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
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
# ---------------------------------------------------------------------------------------
if(SPDLOG_FWRITE_UNLOCKED)
include(CheckSymbolExists)
if(WIN32)
check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED)
else()
check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED)
endif()
if(HAVE_FWRITE_UNLOCKED)
target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED)
endif()
endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Add required libraries for Android CMake build # Add required libraries for Android CMake build
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
@@ -274,9 +290,13 @@ 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)
target_compile_options(spdlog PUBLIC "/utf-8") # fmtlib requires the /utf-8 flag when building with msvc. see https://github.com/fmtlib/fmt/pull/4159 on the
target_compile_options(spdlog_header_only INTERFACE "/utf-8") # purpose of the additional
endif() # "$<$<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_header_only
INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
endif()
endif() endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
@@ -284,12 +304,13 @@ endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
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-)
target_compile_definitions(spdlog PRIVATE _HAS_EXCEPTIONS=0)
endif() endif()
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.
@@ -303,7 +304,7 @@ struct fmt::formatter<my_type> : fmt::formatter<std::string>
{ {
auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) auto format(my_type my, format_context &ctx) const -> decltype(ctx.out())
{ {
return format_to(ctx.out(), "[my_type i={}]", my.i); return fmt::format_to(ctx.out(), "[my_type i={}]", my.i);
} }
}; };
@@ -387,6 +388,9 @@ void android_example()
int main (int argc, char *argv[]) int main (int argc, char *argv[])
{ {
spdlog::cfg::load_env_levels(); spdlog::cfg::load_env_levels();
// or specify the env variable name:
// MYAPP_LEVEL=info,mylogger=trace && ./example
// spdlog::cfg::load_env_levels("MYAPP_LEVEL");
// or from the command line: // or from the command line:
// ./example SPDLOG_LEVEL=info,mylogger=trace // ./example SPDLOG_LEVEL=info,mylogger=trace
// #include "spdlog/cfg/argv.h" // for loading levels from argv // #include "spdlog/cfg/argv.h" // for loading levels from argv
@@ -463,7 +467,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
``` ```
@@ -515,10 +519,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();
@@ -148,6 +148,9 @@ void load_levels_example() {
// Set the log level to "info" and mylogger to "trace": // Set the log level to "info" and mylogger to "trace":
// SPDLOG_LEVEL=info,mylogger=trace && ./example // SPDLOG_LEVEL=info,mylogger=trace && ./example
spdlog::cfg::load_env_levels(); spdlog::cfg::load_env_levels();
// or specify the env variable name:
// MYAPP_LEVEL=info,mylogger=trace && ./example
// spdlog::cfg::load_env_levels("MYAPP_LEVEL");
// or from command line: // or from command line:
// ./example SPDLOG_LEVEL=info,mylogger=trace // ./example SPDLOG_LEVEL=info,mylogger=trace
// #include "spdlog/cfg/argv.h" // for loading levels from argv // #include "spdlog/cfg/argv.h" // for loading levels from argv
@@ -266,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
@@ -281,7 +284,7 @@ struct fmt::formatter<my_type> : fmt::formatter<std::string> {
template <> template <>
struct std::formatter<my_type> : std::formatter<std::string> { struct std::formatter<my_type> : std::formatter<std::string> {
auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) { auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) {
return format_to(ctx.out(), "[my_type i={}]", my.i); return std::format_to(ctx.out(), "[my_type i={}]", my.i);
} }
}; };
#endif #endif
@@ -368,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

@@ -25,8 +25,8 @@
namespace spdlog { namespace spdlog {
namespace cfg { namespace cfg {
inline void load_env_levels() { inline void load_env_levels(const char* var = "SPDLOG_LEVEL") {
auto env_val = details::os::getenv("SPDLOG_LEVEL"); auto env_val = details::os::getenv(var);
if (!env_val.empty()) { if (!env_val.empty()) {
helpers::load_levels(env_val); helpers::load_levels(env_val);
} }

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,12 +364,7 @@ SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view
} }
#endif #endif
#ifndef SPDLOG_USE_STD_FORMAT #if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L
template <typename T, typename... Args>
inline fmt::basic_string_view<T> to_string_view(fmt::basic_format_string<T, Args...> fmt) {
return fmt;
}
#elif __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 {
@@ -101,7 +99,8 @@ SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) {
if (fd_ == nullptr) return; if (fd_ == nullptr) return;
size_t msg_size = buf.size(); size_t msg_size = buf.size();
auto data = buf.data(); auto data = buf.data();
if (std::fwrite(data, 1, msg_size, fd_) != msg_size) {
if (!details::os::fwrite_bytes(data, msg_size, fd_)) {
throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno); throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno);
} }
} }

View File

@@ -265,9 +265,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 +483,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 +563,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
@@ -589,6 +589,18 @@ SPDLOG_INLINE bool fsync(FILE *fp) {
#endif #endif
} }
// Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success.
SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) {
#if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED)
return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes;
#elif defined(SPDLOG_FWRITE_UNLOCKED)
return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes;
#else
return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes;
#endif
}
} // namespace os } // namespace os
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

View File

@@ -114,6 +114,10 @@ SPDLOG_API std::string getenv(const char *field);
// Return true on success. // Return true on success.
SPDLOG_API bool fsync(FILE *fp); SPDLOG_API bool fsync(FILE *fp);
// Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success.
SPDLOG_API bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp);
} // namespace os } // namespace os
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

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

@@ -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

@@ -102,7 +102,7 @@ namespace
template <typename T> template <typename T>
struct formatter<spdlog::details::dump_info<T>, char> { struct formatter<spdlog::details::dump_info<T>, char> {
const char delimiter = ' '; char delimiter = ' ';
bool put_newlines = true; bool put_newlines = true;
bool put_delimiters = true; bool put_delimiters = true;
bool use_uppercase = false; bool use_uppercase = false;
@@ -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

@@ -17,7 +17,6 @@
#include "format.h" // std_string_view #include "format.h" // std_string_view
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template <typename T> struct is_reference_wrapper : std::false_type {}; template <typename T> struct is_reference_wrapper : std::false_type {};
@@ -72,19 +71,13 @@ 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> template <typename Context> class dynamic_format_arg_store {
class dynamic_format_arg_store
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround a GCC template argument substitution bug.
: public basic_format_args<Context>
#endif
{
private: private:
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
template <typename T> struct need_copy { template <typename T> struct need_copy {
static constexpr detail::type mapped_type = static constexpr detail::type mapped_type =
detail::mapped_type_constant<T, Context>::value; detail::mapped_type_constant<T, char_type>::value;
enum { enum {
value = !(detail::is_reference_wrapper<T>::value || value = !(detail::is_reference_wrapper<T>::value ||
@@ -97,7 +90,7 @@ class dynamic_format_arg_store
}; };
template <typename T> template <typename T>
using stored_type = conditional_t< using stored_t = conditional_t<
std::is_convertible<T, std::basic_string<char_type>>::value && std::is_convertible<T, std::basic_string<char_type>>::value &&
!detail::is_reference_wrapper<T>::value, !detail::is_reference_wrapper<T>::value,
std::basic_string<char_type>, T>; std::basic_string<char_type>, T>;
@@ -112,41 +105,37 @@ class dynamic_format_arg_store
friend class basic_format_args<Context>; friend class basic_format_args<Context>;
auto get_types() const -> unsigned long long {
return detail::is_unpacked_bit | data_.size() |
(named_info_.empty()
? 0ULL
: static_cast<unsigned long long>(detail::has_named_args_bit));
}
auto data() const -> const basic_format_arg<Context>* { auto data() const -> const basic_format_arg<Context>* {
return named_info_.empty() ? data_.data() : data_.data() + 1; return named_info_.empty() ? data_.data() : data_.data() + 1;
} }
template <typename T> void emplace_arg(const T& arg) { template <typename T> void emplace_arg(const T& arg) {
data_.emplace_back(detail::make_arg<Context>(arg)); data_.emplace_back(arg);
} }
template <typename T> template <typename T>
void emplace_arg(const detail::named_arg<char_type, T>& arg) { void emplace_arg(const detail::named_arg<char_type, T>& arg) {
if (named_info_.empty()) { if (named_info_.empty())
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
data_.insert(data_.begin(), {zero_ptr, 0}); data_.emplace_back(detail::unwrap(arg.value));
}
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) { auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
data->pop_back(); data->pop_back();
}; };
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
guard{&data_, pop_one}; guard{&data_, pop_one};
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; data_[0] = {named_info_.data(), named_info_.size()};
guard.release(); guard.release();
} }
public: public:
constexpr dynamic_format_arg_store() = default; constexpr dynamic_format_arg_store() = default;
operator basic_format_args<Context>() const {
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
!named_info_.empty());
}
/** /**
* Adds an argument into the dynamic store for later passing to a formatting * Adds an argument into the dynamic store for later passing to a formatting
* function. * function.
@@ -164,7 +153,7 @@ class dynamic_format_arg_store
*/ */
template <typename T> void push_back(const T& arg) { template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value)) if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
else else
emplace_arg(detail::unwrap(arg)); emplace_arg(detail::unwrap(arg));
} }
@@ -200,7 +189,7 @@ class dynamic_format_arg_store
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
if (detail::const_check(need_copy<T>::value)) { if (detail::const_check(need_copy<T>::value)) {
emplace_arg( emplace_arg(
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value))); fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
} else { } else {
emplace_arg(fmt::arg(arg_name, arg.value)); emplace_arg(fmt::arg(arg_name, arg.value));
} }
@@ -210,17 +199,20 @@ class dynamic_format_arg_store
void clear() { void clear() {
data_.clear(); data_.clear();
named_info_.clear(); named_info_.clear();
dynamic_args_ = detail::dynamic_arg_list(); dynamic_args_ = {};
} }
/// Reserves space to store at least `new_cap` arguments including /// Reserves space to store at least `new_cap` arguments including
/// `new_cap_named` named arguments. /// `new_cap_named` named arguments.
void reserve(size_t new_cap, size_t new_cap_named) { void reserve(size_t new_cap, size_t new_cap_named) {
FMT_ASSERT(new_cap >= new_cap_named, FMT_ASSERT(new_cap >= new_cap_named,
"Set of arguments includes set of named arguments"); "set of arguments includes set of named arguments");
data_.reserve(new_cap); data_.reserve(new_cap);
named_info_.reserve(new_cap_named); named_info_.reserve(new_cap_named);
} }
/// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); }
}; };
FMT_END_NAMESPACE FMT_END_NAMESPACE

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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
@@ -330,13 +364,13 @@ FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
namespace detail { namespace detail {
template <typename Char> struct ansi_color_escape { template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, FMT_CONSTEXPR ansi_color_escape(color_type text_color,
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;
@@ -360,7 +394,7 @@ template <typename Char> struct ansi_color_escape {
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');
@@ -412,13 +446,13 @@ template <typename Char> struct ansi_color_escape {
}; };
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto make_foreground_color(detail::color_type foreground) noexcept FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
-> ansi_color_escape<Char> { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(foreground, "\x1b[38;2;"); return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
} }
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto make_background_color(detail::color_type background) noexcept FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
-> ansi_color_escape<Char> { -> ansi_color_escape<Char> {
return ansi_color_escape<Char>(background, "\x1b[48;2;"); return ansi_color_escape<Char>(background, "\x1b[48;2;");
} }
@@ -434,40 +468,33 @@ template <typename Char> inline void reset_color(buffer<Char>& buffer) {
buffer.append(reset_color.begin(), reset_color.end()); buffer.append(reset_color.begin(), reset_color.end());
} }
template <typename T> struct styled_arg : detail::view { template <typename T> struct styled_arg : view {
const T& value; const T& value;
text_style style; text_style style;
styled_arg(const T& v, text_style s) : value(v), style(s) {} styled_arg(const T& v, text_style s) : value(v), style(s) {}
}; };
template <typename Char> template <typename Char>
void vformat_to( void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
buffer<Char>& buf, const text_style& ts, basic_string_view<Char> format_str, basic_format_args<buffered_context<Char>> args) {
basic_format_args<buffered_context<type_identity_t<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 = detail::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 = detail::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 = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
detail::vformat_to(buf, format_str, args, {}); vformat_to(buf, fmt, args);
if (has_style) detail::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()));
@@ -483,9 +510,8 @@ 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, fmt::make_format_args(args...));
} }
/** /**
@@ -498,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);
@@ -522,16 +548,16 @@ 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, fmt::make_format_args(args...)); return fmt::vformat(ts, fmt.str, vargs<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);
@@ -549,9 +575,9 @@ 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, fmt::make_format_args(args...)); return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
} }
template <typename T, typename Char> template <typename T, typename Char>
@@ -560,31 +586,30 @@ struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
const auto& ts = arg.style; const auto& ts = arg.style;
const auto& value = arg.value;
auto out = ctx.out(); auto out = ctx.out();
bool has_style = false; bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true; has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis()); auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
out = std::copy(emphasis.begin(), emphasis.end(), out); out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true; has_style = true;
auto foreground = auto foreground =
detail::make_foreground_color<Char>(ts.get_foreground()); detail::make_foreground_color<Char>(ts.get_foreground());
out = std::copy(foreground.begin(), foreground.end(), out); out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true; has_style = true;
auto background = auto background =
detail::make_background_color<Char>(ts.get_background()); detail::make_background_color<Char>(ts.get_background());
out = std::copy(background.begin(), background.end(), out); out = detail::copy<Char>(background.begin(), background.end(), out);
} }
out = formatter<T, Char>::format(value, ctx); out = formatter<T, Char>::format(arg.value, ctx);
if (has_style) { if (has_style) {
auto reset_color = string_view("\x1b[0m"); auto reset_color = string_view("\x1b[0m");
out = std::copy(reset_color.begin(), reset_color.end(), out); out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
} }
return out; return out;
} }

View File

@@ -19,17 +19,11 @@ 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 T, typename InputIt>
FMT_CONSTEXPR inline auto copy(InputIt begin, InputIt end, counting_iterator it)
-> counting_iterator {
return it + (end - begin);
}
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> {};
namespace detail {
/** /**
* Converts a string literal `s` into a format string that will be parsed at * Converts a string literal `s` into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires C++17 * compile time and converted into efficient formatting code. Requires C++17
@@ -42,22 +36,11 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
* std::string s = fmt::format(FMT_COMPILE("{}"), 42); * std::string s = fmt::format(FMT_COMPILE("{}"), 42);
*/ */
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string, explicit) # define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
#else #else
# define FMT_COMPILE(s) FMT_STRING(s) # define FMT_COMPILE(s) FMT_STRING(s)
#endif #endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N,
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
}
};
#endif
template <typename T, typename... Tail> template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& { auto first(const T& value, const Tail&...) -> const T& {
return value; return value;
@@ -77,6 +60,29 @@ constexpr const auto& get([[maybe_unused]] const T& first,
return detail::get<N - 1>(rest...); return detail::get<N - 1>(rest...);
} }
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <int N, typename T, typename... Args, typename Char>
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
if constexpr (is_static_named_arg<T>()) {
if (name == T::name) return N;
}
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
(void)name; // Workaround an MSVC bug about "unused" parameter.
return -1;
}
# endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
# endif
(void)name;
return -1;
}
template <typename Char, typename... Args> template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name, constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) { type_list<Args...>) {
@@ -149,8 +155,9 @@ template <typename Char, typename T, int N> struct field {
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) { if constexpr (std::is_convertible<T, 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 {
return write<Char>(out, arg);
} }
return write<Char>(out, arg);
} }
}; };
@@ -275,6 +282,7 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
} }
template <typename Char> struct arg_id_handler { template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
constexpr int on_auto() { constexpr int on_auto() {
@@ -282,25 +290,28 @@ template <typename Char> struct arg_id_handler {
return 0; return 0;
} }
constexpr int on_index(int id) { constexpr int on_index(int id) {
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 int on_name(basic_string_view<Char> id) {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id); arg_id = arg_ref<Char>(id);
return 0; return 0;
} }
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id; arg_ref<Char> arg_id;
const Char* arg_id_end; const Char* arg_id_end;
}; };
template <int ID, typename Char> template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) { constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}}; auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler); auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end}; return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
} }
template <typename T, typename Enable = void> struct field_type { template <typename T, typename Enable = void> struct field_type {
@@ -363,18 +374,18 @@ constexpr auto compile_format_string(S fmt) {
constexpr char_type c = constexpr char_type c =
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
static_assert(c == '}' || c == ':', "missing '}' in format string"); static_assert(c == '}' || c == ':', "missing '}' in format string");
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { if constexpr (arg_id_result.kind == arg_id_kind::index) {
static_assert( static_assert(
ID == manual_indexing_id || ID == 0, ID == manual_indexing_id || ID == 0,
"cannot switch from automatic to manual argument indexing"); "cannot switch from automatic to manual argument indexing");
constexpr auto arg_index = arg_id_result.arg_id.val.index; constexpr auto arg_index = arg_id_result.arg_id.index;
return parse_replacement_field_then_tail<get_type<arg_index, Args>, return parse_replacement_field_then_tail<get_type<arg_index, Args>,
Args, arg_id_end_pos, Args, arg_id_end_pos,
arg_index, manual_indexing_id>( arg_index, manual_indexing_id>(
fmt); fmt);
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { } else if constexpr (arg_id_result.kind == arg_id_kind::name) {
constexpr auto arg_index = constexpr auto arg_index =
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
if constexpr (arg_index >= 0) { if constexpr (arg_index >= 0) {
constexpr auto next_id = constexpr auto next_id =
ID != manual_indexing_id ? ID + 1 : manual_indexing_id; ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
@@ -383,8 +394,7 @@ constexpr auto compile_format_string(S fmt) {
arg_index, next_id>(fmt); arg_index, next_id>(fmt);
} else if constexpr (c == '}') { } else if constexpr (c == '}') {
return parse_tail<Args, arg_id_end_pos + 1, ID>( return parse_tail<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name}, runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
fmt);
} else if constexpr (c == ':') { } else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing return unknown_format(); // no type info for specs parsing
} }
@@ -405,7 +415,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) {
@@ -441,7 +451,7 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
} }
template <typename S, typename... Args, template <typename S, typename... Args,
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 std::basic_string<typename S::char_type> format(const S&,
Args&&... args) { Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) { if constexpr (std::is_same<typename S::char_type, char>::value) {
@@ -468,7 +478,7 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
} }
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S()); constexpr auto compiled = detail::compile<Args...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>, if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
@@ -483,7 +493,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
#endif #endif
template <typename OutputIt, typename S, typename... Args, template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(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, Args&&... args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
@@ -493,32 +503,32 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
} }
template <typename S, typename... Args, template <typename S, typename... Args,
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, const Args&... args)
-> size_t { -> size_t {
return fmt::format_to(detail::counting_iterator(), fmt, args...).count(); auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, args...);
return buf.count();
} }
template <typename S, typename... Args, template <typename S, typename... Args,
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, const Args&... args) {
memory_buffer buffer; auto buf = memory_buffer();
fmt::format_to(std::back_inserter(buffer), fmt, args...); fmt::format_to(appender(buf), fmt, args...);
detail::print(f, {buffer.data(), buffer.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... Args,
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, const Args&... args) {
print(stdout, fmt, args...); print(stdout, fmt, args...);
} }
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals { inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() { template <detail::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>; return FMT_COMPILE(Str.data);
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
} }
} // namespace literals } // namespace literals
#endif #endif

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

@@ -14,10 +14,6 @@
# include <climits> # include <climits>
# include <cmath> # include <cmath>
# include <exception> # include <exception>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
# include <locale>
# endif
#endif #endif
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) #if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@@ -26,16 +22,22 @@
#include "format.h" #include "format.h"
#if FMT_USE_LOCALE
# include <locale>
#endif
#ifndef FMT_FUNC
# define FMT_FUNC
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
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.
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
// Chosen instead of std::abort to satisfy Clang in CUDA mode during device abort();
// code pass.
std::terminate();
} }
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,
@@ -61,86 +63,95 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
FMT_ASSERT(out.size() <= inline_buffer_size, ""); FMT_ASSERT(out.size() <= inline_buffer_size, "");
} }
FMT_FUNC void report_error(format_func func, int error_code, FMT_FUNC void do_report_error(format_func func, int error_code,
const char* message) noexcept { const char* message) noexcept {
memory_buffer full_message; memory_buffer full_message;
func(full_message, error_code, message); func(full_message, error_code, message);
// Don't use fwrite_fully because the latter may throw. // Don't use fwrite_all because the latter may throw.
if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)
std::fputc('\n', stderr); std::fputc('\n', stderr);
} }
// A wrapper around fwrite that throws on error. // A wrapper around fwrite that throws on error.
inline void fwrite_fully(const void* ptr, size_t count, FILE* stream) { inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
size_t written = std::fwrite(ptr, 1, count, stream); size_t written = std::fwrite(ptr, 1, count, stream);
if (written < count) if (written < count)
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
} }
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #if FMT_USE_LOCALE
using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale> template <typename Locale>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, std::locale>::value, ""); 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 { template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, std::locale>::value, ""); static_assert(std::is_same<Locale, locale>::value, "");
return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale(); #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 = std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()); auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
auto grouping = facet.grouping(); auto grouping = facet.grouping();
auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();
return {std::move(grouping), thousands_sep}; return {std::move(grouping), thousands_sep};
} }
template <typename Char> template <typename Char>
FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {
return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()) return use_facet<numpunct<Char>>(loc.get<locale>()).decimal_point();
.decimal_point();
} }
#else
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result<Char> {
return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR};
}
template <typename Char> FMT_FUNC Char decimal_point_impl(locale_ref) {
return '.';
}
#endif
#if FMT_USE_LOCALE
FMT_FUNC auto write_loc(appender out, loc_value value, FMT_FUNC auto write_loc(appender out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool { const format_specs& specs, locale_ref loc) -> bool {
#ifdef FMT_STATIC_THOUSANDS_SEPARATOR
value.visit(loc_writer<>{
out, specs, std::string(1, FMT_STATIC_THOUSANDS_SEPARATOR), "\3", "."});
return true;
#else
auto locale = loc.get<std::locale>(); auto locale = loc.get<std::locale>();
// We cannot use the num_put<char> facet because it may produce output in // We cannot use the num_put<char> facet because it may produce output in
// a wrong encoding. // a wrong encoding.
using facet = format_facet<std::locale>; using facet = format_facet<std::locale>;
if (std::has_facet<facet>(locale)) if (std::has_facet<facet>(locale))
return std::use_facet<facet>(locale).put(out, value, specs); return use_facet<facet>(locale).put(out, value, specs);
return facet(locale).put(out, value, specs); return facet(locale).put(out, value, specs);
#endif
} }
#endif
} // namespace detail } // namespace detail
FMT_FUNC void report_error(const char* message) { FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS
// Use FMT_THROW instead of throw to avoid bogus unreachable code warnings
// from MSVC.
FMT_THROW(format_error(message)); FMT_THROW(format_error(message));
#else
fputs(message, stderr);
abort();
#endif
} }
template <typename Locale> typename Locale::id format_facet<Locale>::id; template <typename Locale> typename Locale::id format_facet<Locale>::id;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) { template <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {
auto& numpunct = std::use_facet<std::numpunct<char>>(loc); auto& np = detail::use_facet<detail::numpunct<char>>(loc);
grouping_ = numpunct.grouping(); grouping_ = np.grouping();
if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep());
} }
#if FMT_USE_LOCALE
template <> template <>
FMT_API FMT_FUNC auto format_facet<std::locale>::do_put( FMT_API FMT_FUNC auto format_facet<std::locale>::do_put(
appender out, loc_value val, const format_specs& specs) const -> bool { appender out, loc_value val, const format_specs& specs) const -> bool {
@@ -201,7 +212,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}};
@@ -1086,7 +1097,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));
} }
@@ -1425,7 +1436,7 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
FMT_FUNC void report_system_error(int error_code, FMT_FUNC void report_system_error(int error_code,
const char* message) noexcept { const char* message) noexcept {
report_error(format_system_error, error_code, message); do_report_error(format_system_error, error_code, message);
} }
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
@@ -1438,6 +1449,15 @@ FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {
namespace detail { namespace detail {
FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
locale_ref loc) {
auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string(
fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}});
}
template <typename T> struct span { template <typename T> struct span {
T* data; T* data;
size_t size; size_t size;
@@ -1506,7 +1526,7 @@ 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.
putc_unlocked(0, this->file_); putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr; --this->file_->_IO_write_ptr;
@@ -1615,7 +1635,7 @@ template <typename F> class fallback_file : public file_base<F> {
}; };
#ifndef FMT_USE_FALLBACK_FILE #ifndef FMT_USE_FALLBACK_FILE
# define FMT_USE_FALLBACK_FILE 1 # define FMT_USE_FALLBACK_FILE 0
#endif #endif
template <typename F, template <typename F,
@@ -1692,7 +1712,7 @@ FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args,
auto buffer = memory_buffer(); auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);
if (newline) buffer.push_back('\n'); if (newline) buffer.push_back('\n');
fwrite_fully(buffer.data(), buffer.size(), f); fwrite_all(buffer.data(), buffer.size(), f);
} }
#endif #endif
@@ -1704,7 +1724,7 @@ FMT_FUNC void print(std::FILE* f, string_view text) {
if (write_console(fd, text)) return; if (write_console(fd, text)) return;
} }
#endif #endif
fwrite_fully(text.data(), text.size(), f); fwrite_all(text.data(), text.size(), f);
} }
} // namespace detail } // namespace detail

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
#include "xchar.h"
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead

View File

@@ -118,7 +118,7 @@ FMT_API void format_windows_error(buffer<char>& out, int error_code,
const char* message) noexcept; const char* message) noexcept;
} }
FMT_API std::system_error vwindows_error(int error_code, string_view format_str, FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
format_args args); format_args args);
/** /**
@@ -146,10 +146,10 @@ FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
* "cannot open file '{}'", filename); * "cannot open file '{}'", filename);
* } * }
*/ */
template <typename... Args> template <typename... T>
std::system_error windows_error(int error_code, string_view message, auto windows_error(int error_code, string_view message, const T&... args)
const Args&... args) { -> std::system_error {
return vwindows_error(error_code, message, fmt::make_format_args(args...)); return vwindows_error(error_code, message, vargs<T...>{{args...}});
} }
// Reports a Windows error without throwing an exception. // Reports a Windows error without throwing an exception.
@@ -164,8 +164,8 @@ inline auto system_category() noexcept -> const std::error_category& {
// std::system is not available on some platforms such as iOS (#2248). // std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__ #ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>> template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& format_str, Args&&... args) { void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(format_str, args...)).c_str()); std::system(format("say \"{}\"", format(fmt, args...)).c_str());
} }
#endif #endif
@@ -176,24 +176,24 @@ class buffered_file {
friend class file; friend class file;
explicit buffered_file(FILE* f) : file_(f) {} inline explicit buffered_file(FILE* f) : file_(f) {}
public: public:
buffered_file(const buffered_file&) = delete; buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete; void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file. // Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {} inline buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept; FMT_API ~buffered_file() noexcept;
public: public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) { inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr; other.file_ = nullptr;
} }
auto operator=(buffered_file&& other) -> buffered_file& { inline auto operator=(buffered_file&& other) -> buffered_file& {
close(); close();
file_ = other.file_; file_ = other.file_;
other.file_ = nullptr; other.file_ = nullptr;
@@ -207,13 +207,13 @@ class buffered_file {
FMT_API void close(); FMT_API void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; } inline auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int; FMT_API auto descriptor() const -> int;
template <typename... T> template <typename... T>
inline void print(string_view fmt, const T&... args) { inline void print(string_view fmt, const T&... args) {
const auto& vargs = fmt::make_format_args(args...); fmt::vargs<T...> vargs = {{args...}};
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs) detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
: fmt::vprint(file_, fmt, vargs); : fmt::vprint(file_, fmt, vargs);
} }
@@ -248,7 +248,7 @@ class FMT_API file {
}; };
// Constructs a file object which doesn't represent any file. // Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {} inline file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file. // Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag); file(cstring_view path, int oflag);
@@ -257,10 +257,10 @@ class FMT_API file {
file(const file&) = delete; file(const file&) = delete;
void operator=(const file&) = delete; void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw. // Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& { inline auto operator=(file&& other) -> file& {
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
@@ -271,7 +271,7 @@ class FMT_API file {
~file() noexcept; ~file() noexcept;
// Returns the file descriptor. // Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; } inline auto descriptor() const noexcept -> int { return fd_; }
// Closes the file. // Closes the file.
void close(); void close();
@@ -324,9 +324,9 @@ auto getpagesize() -> long;
namespace detail { namespace detail {
struct buffer_size { struct buffer_size {
buffer_size() = default; constexpr buffer_size() = default;
size_t value = 0; size_t value = 0;
auto operator=(size_t val) const -> buffer_size { FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size(); auto bs = buffer_size();
bs.value = val; bs.value = val;
return bs; return bs;
@@ -337,7 +337,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC; int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {} constexpr ostream_params() {}
template <typename... T> template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) { ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@@ -358,59 +358,47 @@ struct ostream_params {
# endif # endif
}; };
class file_buffer final : public buffer<char> { } // namespace detail
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream : private detail::buffer<char> {
private: private:
file file_; file file_;
FMT_API static void grow(buffer<char>& buf, size_t); ostream(cstring_view path, const detail::ostream_params& params);
static void grow(buffer<char>& buf, size_t);
public: public:
FMT_API file_buffer(cstring_view path, const ostream_params& params); ostream(ostream&& other) noexcept;
FMT_API file_buffer(file_buffer&& other) noexcept; ~ostream();
FMT_API ~file_buffer();
void flush() { operator writer() {
detail::buffer<char>& buf = *this;
return buf;
}
inline void flush() {
if (size() == 0) return; if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0])); file_.write(data(), size() * sizeof(data()[0]));
clear(); clear();
} }
void close() {
flush();
file_.close();
}
};
} // namespace detail
constexpr auto buffer_size = detail::buffer_size();
/// A fast output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream {
private:
FMT_MSC_WARNING(suppress : 4251)
detail::file_buffer buffer_;
ostream(cstring_view path, const detail::ostream_params& params)
: buffer_(path, params) {}
public:
ostream(ostream&& other) : buffer_(std::move(other.buffer_)) {}
~ostream();
void flush() { buffer_.flush(); }
template <typename... T> template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream; friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { buffer_.close(); } inline void close() {
flush();
file_.close();
}
/// Formats `args` according to specifications in `fmt` and writes the /// Formats `args` according to specifications in `fmt` and writes the
/// output to the file. /// output to the file.
template <typename... T> void print(format_string<T...> fmt, T&&... args) { template <typename... T> void print(format_string<T...> fmt, T&&... args) {
vformat_to(appender(buffer_), fmt, fmt::make_format_args(args...)); vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
} }
}; };

View File

@@ -22,6 +22,14 @@
#include "chrono.h" // formatbuf #include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
@@ -35,53 +43,18 @@ class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
}; };
#if FMT_MSC_VERSION #if FMT_MSVC_STL_UPDATE
template class file_access<file_access_tag, std::filebuf, template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>; &std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*; auto get_file(std::filebuf&) -> FILE*;
#endif #endif
inline auto write_ostream_unicode(std::ostream& os, fmt::string_view data)
-> bool {
FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = get_file(*buf);
else
return false;
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
auto* rdbuf = os.rdbuf();
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
f = sfbuf->file();
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
f = fbuf->file();
else
return false;
#else
ignore_unused(os, data, f);
#endif
#ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
return write_console(fd, data);
}
}
#endif
return false;
}
inline auto write_ostream_unicode(std::wostream&,
fmt::basic_string_view<wchar_t>) -> bool {
return false;
}
// Write the content of buf to os. // Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing. // It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; using unsigned_streamsize = make_unsigned_t<std::streamsize>;
unsigned_streamsize size = buf.size(); unsigned_streamsize size = buf.size();
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>()); unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
do { do {
@@ -92,21 +65,9 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
} while (size != 0); } while (size != 0);
} }
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value) {
auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
output.imbue(std::locale::classic()); // The default is always unlocalized.
#endif
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
}
template <typename T> struct streamed_view { template <typename T> struct streamed_view {
const T& value; const T& value;
}; };
} // namespace detail } // namespace detail
// Formats an object of type T that has an overloaded ostream operator<<. // Formats an object of type T that has an overloaded ostream operator<<.
@@ -117,7 +78,11 @@ struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
template <typename T, typename Context> template <typename T, typename Context>
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
auto buffer = basic_memory_buffer<Char>(); auto buffer = basic_memory_buffer<Char>();
detail::format_value(buffer, value); auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
auto&& output = std::basic_ostream<Char>(&formatbuf);
output.imbue(std::locale::classic()); // The default is always unlocalized.
output << value;
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
return formatter<basic_string_view<Char>, Char>::format( return formatter<basic_string_view<Char>, Char>::format(
{buffer.data(), buffer.size()}, ctx); {buffer.data(), buffer.size()}, ctx);
} }
@@ -148,24 +113,30 @@ constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
return {value}; return {value};
} }
namespace detail { inline void vprint(std::ostream& os, string_view fmt, format_args args) {
inline void vprint_directly(std::ostream& os, string_view format_str,
format_args args) {
auto buffer = memory_buffer(); auto buffer = memory_buffer();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer); FILE* f = nullptr;
} #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
} // namespace detail f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
FMT_EXPORT template <typename Char> auto* rdbuf = os.rdbuf();
void vprint(std::basic_ostream<Char>& os, if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
basic_string_view<type_identity_t<Char>> format_str, f = sfbuf->file();
typename detail::vformat_args<Char>::type args) { else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
auto buffer = basic_memory_buffer<Char>(); f = fbuf->file();
detail::vformat_to(buffer, format_str, args); #endif
if (detail::write_ostream_unicode(os, {buffer.data(), buffer.size()})) return; #ifdef _WIN32
if (f) {
int fd = _fileno(f);
if (_isatty(fd)) {
os.flush();
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
}
}
#endif
detail::ignore_unused(f);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }
@@ -178,32 +149,17 @@ void vprint(std::basic_ostream<Char>& os,
*/ */
FMT_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) { void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
const auto& vargs = fmt::make_format_args(args...); fmt::vargs<T...> vargs = {{args...}};
if (detail::use_utf8()) if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
vprint(os, fmt, vargs); auto buffer = memory_buffer();
else detail::vformat_to(buffer, fmt.str, vargs);
detail::vprint_directly(os, fmt, vargs); detail::write_buffer(os, buffer);
}
FMT_EXPORT
template <typename... Args>
void print(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_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_EXPORT
template <typename... Args>
void println(std::wostream& os,
basic_format_string<wchar_t, type_identity_t<Args>...> fmt,
Args&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<Args>(args)...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -33,8 +33,9 @@ template <typename Char> class basic_printf_context {
public: public:
using char_type = Char; using char_type = Char;
using parse_context_type = basic_format_parse_context<Char>; using parse_context_type = parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>; template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are /// Constructs a `printf_context` object. References to the arguments are
/// stored in the context object so make sure they have appropriate lifetimes. /// stored in the context object so make sure they have appropriate lifetimes.
@@ -54,6 +55,23 @@ template <typename Char> class basic_printf_context {
namespace detail { namespace detail {
// Return the result via the out param to workaround gcc bug 77539.
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
for (out = first; out != last; ++out) {
if (*out == value) return true;
}
return false;
}
template <>
inline auto find<false, char>(const char* first, const char* last, char value,
const char*& out) -> bool {
out =
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
return out != nullptr;
}
// Checks if a value fits in int - used to avoid warnings about comparing // Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers. // signed and unsigned integers.
template <bool IsSigned> struct int_checker { template <bool IsSigned> struct int_checker {
@@ -61,7 +79,7 @@ template <bool IsSigned> struct int_checker {
unsigned max = to_unsigned(max_value<int>()); unsigned max = to_unsigned(max_value<int>());
return value <= max; return value <= max;
} }
static auto fits_in_int(bool) -> bool { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
template <> struct int_checker<true> { template <> struct int_checker<true> {
@@ -69,7 +87,7 @@ template <> struct int_checker<true> {
return value >= (std::numeric_limits<int>::min)() && return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>(); value <= max_value<int>();
} }
static auto fits_in_int(int) -> bool { return true; } inline static auto fits_in_int(int) -> bool { return true; }
}; };
struct printf_precision_handler { struct printf_precision_handler {
@@ -127,25 +145,19 @@ template <typename T, typename Context> class arg_converter {
using target_type = conditional_t<std::is_same<T, void>::value, U, T>; using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
if (const_check(sizeof(target_type) <= sizeof(int))) { if (const_check(sizeof(target_type) <= sizeof(int))) {
// Extra casts are used to silence warnings. // Extra casts are used to silence warnings.
if (is_signed) { using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
auto n = static_cast<int>(static_cast<target_type>(value)); if (is_signed)
arg_ = detail::make_arg<Context>(n); arg_ = static_cast<int>(static_cast<target_type>(value));
} else { else
using unsigned_type = typename make_unsigned_or_bool<target_type>::type; arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
auto n = static_cast<unsigned>(static_cast<unsigned_type>(value));
arg_ = detail::make_arg<Context>(n);
}
} else { } else {
if (is_signed) { // glibc's printf doesn't sign extend arguments of smaller types:
// glibc's printf doesn't sign extend arguments of smaller types: // std::printf("%lld", -42); // prints "4294967254"
// std::printf("%lld", -42); // prints "4294967254" // but we don't have to do the same because it's a UB.
// but we don't have to do the same because it's a UB. if (is_signed)
auto n = static_cast<long long>(value); arg_ = static_cast<long long>(value);
arg_ = detail::make_arg<Context>(n); else
} else { arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
auto n = static_cast<typename make_unsigned_or_bool<U>::type>(value);
arg_ = detail::make_arg<Context>(n);
}
} }
} }
@@ -172,8 +184,7 @@ template <typename Context> class char_converter {
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
auto c = static_cast<typename Context::char_type>(value); arg_ = static_cast<typename Context::char_type>(value);
arg_ = detail::make_arg<Context>(c);
} }
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@@ -194,13 +205,13 @@ class printf_width_handler {
format_specs& specs_; format_specs& specs_;
public: public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned { auto operator()(T value) -> unsigned {
auto width = static_cast<uint32_or_64_or_128_t<T>>(value); auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
if (detail::is_negative(value)) { if (detail::is_negative(value)) {
specs_.align = align::left; specs_.set_align(align::left);
width = 0 - width; width = 0 - width;
} }
unsigned int_max = to_unsigned(max_value<int>()); unsigned int_max = to_unsigned(max_value<int>());
@@ -234,69 +245,74 @@ class printf_arg_formatter : public arg_formatter<Char> {
void write_null_pointer(bool is_string = false) { void write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = presentation_type::none; s.set_type(presentation_type::none);
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s); write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
} }
template <typename T> void write(T value) {
detail::write<Char>(this->out, value, this->specs, this->locale);
}
public: public:
printf_arg_formatter(basic_appender<Char> iter, format_specs& s, printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
context_type& ctx) context_type& ctx)
: base(make_arg_formatter(iter, s)), context_(ctx) {} : base(make_arg_formatter(iter, s)), context_(ctx) {}
void operator()(monostate value) { base::operator()(value); } void operator()(monostate value) { write(value); }
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
void operator()(T value) { void operator()(T value) {
// MSVC2013 fails to compile separate overloads for bool and Char so use // MSVC2013 fails to compile separate overloads for bool and Char so use
// std::is_same instead. // std::is_same instead.
if (!std::is_same<T, Char>::value) { if (!std::is_same<T, Char>::value) {
base::operator()(value); write(value);
return; return;
} }
format_specs s = this->specs; format_specs s = this->specs;
if (s.type != presentation_type::none && s.type != presentation_type::chr) { if (s.type() != presentation_type::none &&
s.type() != presentation_type::chr) {
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
} }
s.sign = sign::none; s.set_sign(sign::none);
s.alt = false; s.clear_alt();
s.fill = ' '; // Ignore '0' flag for char types. s.set_fill(' '); // Ignore '0' flag for char types.
// align::numeric needs to be overwritten here since the '0' flag is // align::numeric needs to be overwritten here since the '0' flag is
// ignored for non-numeric types // ignored for non-numeric types
if (s.align == align::none || s.align == align::numeric) if (s.align() == align::none || s.align() == align::numeric)
s.align = align::right; s.set_align(align::right);
write<Char>(this->out, static_cast<Char>(value), s); detail::write<Char>(this->out, static_cast<Char>(value), s);
} }
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
void operator()(T value) { void operator()(T value) {
base::operator()(value); write(value);
} }
void operator()(const char* value) { void operator()(const char* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(this->specs.type != presentation_type::pointer); write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
void operator()(const wchar_t* value) { void operator()(const wchar_t* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(this->specs.type != presentation_type::pointer); write_null_pointer(this->specs.type() != presentation_type::pointer);
} }
void operator()(basic_string_view<Char> value) { base::operator()(value); } void operator()(basic_string_view<Char> value) { write(value); }
void operator()(const void* value) { void operator()(const void* value) {
if (value) if (value)
base::operator()(value); write(value);
else else
write_null_pointer(); write_null_pointer();
} }
void operator()(typename basic_format_arg<context_type>::handle handle) { void operator()(typename basic_format_arg<context_type>::handle handle) {
auto parse_ctx = basic_format_parse_context<Char>({}); auto parse_ctx = parse_context<Char>({});
handle.format(parse_ctx, context_); handle.format(parse_ctx, context_);
} }
}; };
@@ -305,23 +321,14 @@ template <typename Char>
void parse_flags(format_specs& specs, const Char*& it, const Char* end) { void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
for (; it != end; ++it) { for (; it != end; ++it) {
switch (*it) { switch (*it) {
case '-': case '-': specs.set_align(align::left); break;
specs.align = align::left; case '+': specs.set_sign(sign::plus); break;
break; case '0': specs.set_fill('0'); break;
case '+':
specs.sign = sign::plus;
break;
case '0':
specs.fill = '0';
break;
case ' ': case ' ':
if (specs.sign != sign::plus) specs.sign = sign::space; if (specs.sign() != sign::plus) specs.set_sign(sign::space);
break; break;
case '#': case '#': specs.set_alt(); break;
specs.alt = true; default: return;
break;
default:
return;
} }
} }
} }
@@ -339,7 +346,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs,
++it; ++it;
arg_index = value != -1 ? value : max_value<int>(); arg_index = value != -1 ? value : max_value<int>();
} else { } else {
if (c == '0') specs.fill = '0'; if (c == '0') specs.set_fill('0');
if (value != 0) { if (value != 0) {
// Nonzero value means that we parsed width and don't need to // Nonzero value means that we parsed width and don't need to
// parse it or flags again, so return now. // parse it or flags again, so return now.
@@ -369,43 +376,22 @@ inline auto parse_printf_presentation_type(char c, type t, bool& upper)
using pt = presentation_type; using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
switch (c) { switch (c) {
case 'd': case 'd': return in(t, integral_set) ? pt::dec : pt::none;
return in(t, integral_set) ? pt::dec : pt::none; case 'o': return in(t, integral_set) ? pt::oct : pt::none;
case 'o': case 'X': upper = true; FMT_FALLTHROUGH;
return in(t, integral_set) ? pt::oct : pt::none; case 'x': return in(t, integral_set) ? pt::hex : pt::none;
case 'X': case 'E': upper = true; FMT_FALLTHROUGH;
upper = true; case 'e': return in(t, float_set) ? pt::exp : pt::none;
FMT_FALLTHROUGH; case 'F': upper = true; FMT_FALLTHROUGH;
case 'x': case 'f': return in(t, float_set) ? pt::fixed : pt::none;
return in(t, integral_set) ? pt::hex : pt::none; case 'G': upper = true; FMT_FALLTHROUGH;
case 'E': case 'g': return in(t, float_set) ? pt::general : pt::none;
upper = true; case 'A': upper = true; FMT_FALLTHROUGH;
FMT_FALLTHROUGH; case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
case 'e': case 'c': return in(t, integral_set) ? pt::chr : pt::none;
return in(t, float_set) ? pt::exp : pt::none; case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'F': case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
upper = true; default: return pt::none;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G':
upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
return in(t, string_set | cstring_set) ? pt::string : pt::none;
case 'p':
return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
default:
return pt::none;
} }
} }
@@ -415,7 +401,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
using iterator = basic_appender<Char>; using iterator = basic_appender<Char>;
auto out = iterator(buf); auto out = iterator(buf);
auto context = basic_printf_context<Char>(out, args); auto context = basic_printf_context<Char>(out, args);
auto parse_ctx = basic_format_parse_context<Char>(format); auto parse_ctx = parse_context<Char>(format);
// Returns the argument with specified index or, if arg_index is -1, the next // Returns the argument with specified index or, if arg_index is -1, the next
// argument. // argument.
@@ -444,7 +430,7 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start))); write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
auto specs = format_specs(); auto specs = format_specs();
specs.align = align::right; specs.set_align(align::right);
// Parse argument index, flags and width. // Parse argument index, flags and width.
int arg_index = parse_header(it, end, specs, get_arg); int arg_index = parse_header(it, end, specs, get_arg);
@@ -468,9 +454,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto arg = get_arg(arg_index); auto arg = get_arg(arg_index);
// For d, i, o, u, x, and X conversion specifiers, if a precision is // For d, i, o, u, x, and X conversion specifiers, if a precision is
// specified, the '0' flag is ignored // specified, the '0' flag is ignored
if (specs.precision >= 0 && arg.is_integral()) { if (specs.precision >= 0 && is_integral_type(arg.type())) {
// Ignore '0' for non-numeric types or if '-' present. // Ignore '0' for non-numeric types or if '-' present.
specs.fill = ' '; specs.set_fill(' ');
} }
if (specs.precision >= 0 && arg.type() == type::cstring_type) { if (specs.precision >= 0 && arg.type() == type::cstring_type) {
auto str = arg.visit(get_cstring<Char>()); auto str = arg.visit(get_cstring<Char>());
@@ -478,15 +464,16 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
auto nul = std::find(str, str_end, Char()); auto nul = std::find(str, str_end, Char());
auto sv = basic_string_view<Char>( auto sv = basic_string_view<Char>(
str, to_unsigned(nul != str_end ? nul - str : specs.precision)); str, to_unsigned(nul != str_end ? nul - str : specs.precision));
arg = make_arg<basic_printf_context<Char>>(sv); arg = sv;
} }
if (specs.alt && arg.visit(is_zero_int())) specs.alt = false; if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
if (specs.fill.template get<Char>() == '0') { if (specs.fill_unit<Char>() == '0') {
if (arg.is_arithmetic() && specs.align != align::left) if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
specs.align = align::numeric; specs.set_align(align::numeric);
else } else {
specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-' // Ignore '0' flag for non-numeric types or if '-' flag is also present.
// flag is also present. specs.set_fill(' ');
}
} }
// Parse length and convert the argument to the required type. // Parse length and convert the argument to the required type.
@@ -511,44 +498,34 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
convert_arg<long>(arg, t); convert_arg<long>(arg, t);
} }
break; break;
case 'j': case 'j': convert_arg<intmax_t>(arg, t); break;
convert_arg<intmax_t>(arg, t); case 'z': convert_arg<size_t>(arg, t); break;
break; case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
case 'z':
convert_arg<size_t>(arg, t);
break;
case 't':
convert_arg<std::ptrdiff_t>(arg, t);
break;
case 'L': case 'L':
// printf produces garbage when 'L' is omitted for long double, no // printf produces garbage when 'L' is omitted for long double, no
// need to do the same. // need to do the same.
break; break;
default: default: --it; convert_arg<void>(arg, c);
--it;
convert_arg<void>(arg, c);
} }
// Parse type. // Parse type.
if (it == end) report_error("invalid format string"); if (it == end) report_error("invalid format string");
char type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (is_integral_type(arg.type())) {
// Normalize type. // Normalize type.
switch (type) { switch (type) {
case 'i': case 'i':
case 'u': case 'u': type = 'd'; break;
type = 'd';
break;
case 'c': case 'c':
arg.visit(char_converter<basic_printf_context<Char>>(arg)); arg.visit(char_converter<basic_printf_context<Char>>(arg));
break; break;
} }
} }
bool upper = false; bool upper = false;
specs.type = parse_printf_presentation_type(type, arg.type(), upper); specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
if (specs.type == presentation_type::none) if (specs.type() == presentation_type::none)
report_error("invalid format specifier"); report_error("invalid format specifier");
specs.upper = upper; if (upper) specs.set_upper();
start = it; start = it;
@@ -583,7 +560,7 @@ inline auto vsprintf(basic_string_view<Char> fmt,
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args); detail::vprintf(buf, fmt, args);
return to_string(buf); return {buf.data(), buf.size()};
} }
/** /**
@@ -594,7 +571,7 @@ 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 = char_t<S>> template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> { inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt), return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...)); fmt::make_format_args<basic_printf_context<Char>>(args...));
@@ -619,7 +596,7 @@ 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 = char_t<S>> template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int { inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt), return vfprintf(f, detail::to_string_view(fmt),
make_printf_args<Char>(args...)); make_printf_args<Char>(args...));

View File

@@ -44,18 +44,6 @@ template <typename T> class is_set {
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value; !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
}; };
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1800
# define FMT_DECLTYPE_RETURN(val) \
->decltype(val) { return val; } \
static_assert( \
true, "") // This makes it so that a semicolon is required after the
// macro, which helps clang-format handle the formatting.
// C array overload // C array overload
template <typename T, std::size_t N> template <typename T, std::size_t N>
auto range_begin(const T (&arr)[N]) -> const T* { auto range_begin(const T (&arr)[N]) -> const T* {
@@ -76,9 +64,13 @@ struct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),
// Member function overloads. // Member function overloads.
template <typename T> template <typename T>
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin()); auto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {
return static_cast<T&&>(rng).begin();
}
template <typename T> template <typename T>
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end()); auto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {
return static_cast<T&&>(rng).end();
}
// ADL overloads. Only participate in overload resolution if member functions // ADL overloads. Only participate in overload resolution if member functions
// are not found. // are not found.
@@ -115,17 +107,16 @@ struct has_mutable_begin_end<
// SFINAE properly unless there are distinct types // SFINAE properly unless there are distinct types
int>> : std::true_type {}; int>> : std::true_type {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
template <typename T> template <typename T>
struct is_range_<T, void> struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value || : std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {}; has_mutable_begin_end<T>::value)> {};
# undef FMT_DECLTYPE_RETURN
#endif
// tuple_size and tuple_element check. // tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U, typename V = typename std::remove_cv<U>::type>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);
template <typename> static void check(...); template <typename> static void check(...);
public: public:
@@ -266,12 +257,12 @@ template <range_format K>
using range_format_constant = std::integral_constant<range_format, K>; using range_format_constant = std::integral_constant<range_format, K>;
// These are not generic lambdas for compatibility with C++11. // These are not generic lambdas for compatibility with C++11.
template <typename ParseContext> struct parse_empty_specs { template <typename Char> struct parse_empty_specs {
template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) { template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {
f.parse(ctx); f.parse(ctx);
detail::maybe_set_debug_format(f, true); detail::maybe_set_debug_format(f, true);
} }
ParseContext& ctx; parse_context<Char>& ctx;
}; };
template <typename FormatContext> struct format_tuple_element { template <typename FormatContext> struct format_tuple_element {
using char_type = typename FormatContext::char_type; using char_type = typename FormatContext::char_type;
@@ -327,11 +318,17 @@ struct formatter<Tuple, Char,
closing_bracket_ = close; closing_bracket_ = close;
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
if (it != ctx.end() && *it != '}') report_error("invalid format specifier"); auto end = ctx.end();
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); if (it != end && detail::to_ascii(*it) == 'n') {
++it;
set_brackets({}, {});
set_separator({});
}
if (it != end && *it != '}') report_error("invalid format specifier");
ctx.advance_to(it);
detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it; return it;
} }
@@ -352,38 +349,17 @@ template <typename T, typename Char> struct is_range {
}; };
namespace detail { namespace detail {
template <typename Context> struct range_mapper {
using mapper = arg_mapper<Context>;
template <typename T,
FMT_ENABLE_IF(has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value) -> T&& {
return static_cast<T&&>(value);
}
template <typename T,
FMT_ENABLE_IF(!has_formatter<remove_cvref_t<T>, Context>::value)>
static auto map(T&& value)
-> decltype(mapper().map(static_cast<T&&>(value))) {
return mapper().map(static_cast<T&&>(value));
}
};
template <typename Char, typename Element> template <typename Char, typename Element>
using range_formatter_type = using range_formatter_type = formatter<remove_cvref_t<Element>, Char>;
formatter<remove_cvref_t<decltype(range_mapper<buffered_context<Char>>{}
.map(std::declval<Element>()))>,
Char>;
template <typename R> template <typename R>
using maybe_const_range = using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>; conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char> template <typename R, typename Char>
struct is_formattable_delayed struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {}; : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail } // namespace detail
template <typename...> struct conjunction : std::true_type {}; template <typename...> struct conjunction : std::true_type {};
@@ -415,7 +391,7 @@ struct range_formatter<
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
for (; it != end; ++it) buf.push_back(*it); for (; it != end; ++it) buf.push_back(*it);
auto specs = format_specs(); auto specs = format_specs();
specs.type = presentation_type::debug; specs.set_type(presentation_type::debug);
return detail::write<Char>( return detail::write<Char>(
out, basic_string_view<Char>(buf.data(), buf.size()), specs); out, basic_string_view<Char>(buf.data(), buf.size()), specs);
} }
@@ -443,8 +419,7 @@ struct range_formatter<
closing_bracket_ = close; closing_bracket_ = close;
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
detail::maybe_set_debug_format(underlying_, true); detail::maybe_set_debug_format(underlying_, true);
@@ -486,7 +461,6 @@ struct range_formatter<
template <typename R, typename FormatContext> template <typename R, typename FormatContext>
auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {
auto mapper = detail::range_mapper<buffered_context<Char>>();
auto out = ctx.out(); auto out = ctx.out();
auto it = detail::range_begin(range); auto it = detail::range_begin(range);
auto end = detail::range_end(range); auto end = detail::range_end(range);
@@ -498,7 +472,7 @@ struct range_formatter<
if (i > 0) out = detail::copy<Char>(separator_, out); if (i > 0) out = detail::copy<Char>(separator_, out);
ctx.advance_to(out); ctx.advance_to(out);
auto&& item = *it; // Need an lvalue auto&& item = *it; // Need an lvalue
out = underlying_.format(mapper.map(item), ctx); out = underlying_.format(item, ctx);
++i; ++i;
} }
out = detail::copy<Char>(closing_bracket_, out); out = detail::copy<Char>(closing_bracket_, out);
@@ -521,13 +495,8 @@ struct formatter<
range_format_kind<R, Char>::value != range_format::disabled && range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map && range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string && range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string> range_format_kind<R, Char>::value != range_format::debug_string>,
// Workaround a bug in MSVC 2015 and earlier. detail::is_formattable_delayed<R, Char>>::value>> {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
private: private:
using range_type = detail::maybe_const_range<R>; using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_; range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
@@ -543,8 +512,7 @@ struct formatter<
detail::string_literal<Char, '}'>{}); detail::string_literal<Char, '}'>{});
} }
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return range_formatter_.parse(ctx); return range_formatter_.parse(ctx);
} }
@@ -559,7 +527,9 @@ struct formatter<
template <typename R, typename Char> template <typename R, typename Char>
struct formatter< struct formatter<
R, Char, R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> { enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
private: private:
using map_type = detail::maybe_const_range<R>; using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>; using element_type = detail::uncvref_type<map_type>;
@@ -571,8 +541,7 @@ struct formatter<
public: public:
FMT_CONSTEXPR formatter() {} FMT_CONSTEXPR formatter() {}
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it != end) { if (it != end) {
@@ -586,7 +555,7 @@ struct formatter<
} }
ctx.advance_to(it); ctx.advance_to(it);
} }
detail::for_each(formatters_, detail::parse_empty_specs<ParseContext>{ctx}); detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});
return it; return it;
} }
@@ -596,12 +565,11 @@ struct formatter<
basic_string_view<Char> open = detail::string_literal<Char, '{'>{}; basic_string_view<Char> open = detail::string_literal<Char, '{'>{};
if (!no_delimiters_) out = detail::copy<Char>(open, out); if (!no_delimiters_) out = detail::copy<Char>(open, out);
int i = 0; int i = 0;
auto mapper = detail::range_mapper<buffered_context<Char>>();
basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{}; basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};
for (auto&& value : map) { for (auto&& value : map) {
if (i > 0) out = detail::copy<Char>(sep, out); if (i > 0) out = detail::copy<Char>(sep, out);
ctx.advance_to(out); ctx.advance_to(out);
detail::for_each2(formatters_, mapper.map(value), detail::for_each2(formatters_, value,
detail::format_tuple_element<FormatContext>{ detail::format_tuple_element<FormatContext>{
0, ctx, detail::string_literal<Char, ':', ' '>{}}); 0, ctx, detail::string_literal<Char, ':', ' '>{}});
++i; ++i;
@@ -631,8 +599,7 @@ struct formatter<
formatter<string_type, Char> underlying_; formatter<string_type, Char> underlying_;
public: public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
@@ -673,22 +640,22 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
#endif #endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_; formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view_ref = conditional_t<std::is_copy_constructible<It>::value, using view = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&, const join_view<It, Sentinel, Char>,
join_view<It, Sentinel, Char>&&>; join_view<It, Sentinel, Char>>;
public: public:
using nonlocking = void; using nonlocking = void;
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
return value_formatter_.parse(ctx); return value_formatter_.parse(ctx);
} }
template <typename FormatContext> template <typename FormatContext>
auto format(view_ref& value, FormatContext& ctx) const auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
-> decltype(ctx.out()) { using iter =
auto it = std::forward<view_ref>(value).begin; conditional_t<std::is_copy_constructible<view>::value, It, It&>;
iter it = value.begin;
auto out = ctx.out(); auto out = ctx.out();
if (it == value.end) return out; if (it == value.end) return out;
out = value_formatter_.format(*it, ctx); out = value_formatter_.format(*it, ctx);
@@ -703,39 +670,11 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
}; };
/// Returns a view that formats the iterator range `[begin, end)` with elements template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
/// separated by `sep`. const Tuple& tuple;
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s) tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {} : tuple(t), sep{s} {}
}; };
@@ -746,37 +685,36 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0 # define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif #endif
template <typename Char, typename... T> template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, T...>, Char> { struct formatter<tuple_join_view<Char, Tuple>, Char,
template <typename ParseContext> enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>()); return do_parse(ctx, std::tuple_size<Tuple>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value, auto format(const tuple_join_view<Char, Tuple>& value,
FormatContext& ctx) const -> typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, return do_format(value, ctx, std::tuple_size<Tuple>());
std::integral_constant<size_t, sizeof...(T)>());
} }
private: private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_; decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
template <typename ParseContext> FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, 0>) std::integral_constant<size_t, 0>)
-> decltype(ctx.begin()) { -> const Char* {
return ctx.begin(); return ctx.begin();
} }
template <typename ParseContext, size_t N> template <size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx, FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, N>) std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) { -> const Char* {
auto end = ctx.begin(); auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS #if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx); end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) { if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>()); auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1) if (end != end1)
@@ -787,18 +725,20 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
} }
template <typename FormatContext> template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx, auto do_format(const tuple_join_view<Char, Tuple>&, 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, T...>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
auto out = std::get<sizeof...(T) - N>(formatters_) using std::get;
.format(std::get<sizeof...(T) - N>(value.tuple), ctx); auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out; if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out); out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out); ctx.advance_to(out);
@@ -834,18 +774,46 @@ 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);
} }
}; };
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
/** /**
* Returns an object that formats `std::tuple` with elements separated by `sep`. * Returns an object that formats `std::tuple` with elements separated by `sep`.
* *
@@ -855,9 +823,9 @@ FMT_BEGIN_EXPORT
* fmt::print("{}", fmt::join(t, ", ")); * fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a * // Output: 1, a
*/ */
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, T...> { -> tuple_join_view<char, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@@ -17,6 +17,7 @@
# include <complex> # include <complex>
# include <cstdlib> # include <cstdlib>
# include <exception> # include <exception>
# include <functional>
# include <memory> # include <memory>
# include <thread> # include <thread>
# include <type_traits> # include <type_traits>
@@ -26,7 +27,8 @@
// 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
# if FMT_HAS_INCLUDE(<filesystem>) # if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# include <filesystem> # include <filesystem>
# endif # endif
# if FMT_HAS_INCLUDE(<variant>) # if FMT_HAS_INCLUDE(<variant>)
@@ -111,7 +113,6 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
} // namespace detail } // namespace detail
FMT_EXPORT
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_;
@@ -122,14 +123,16 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
public: public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
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; if (it == end) return it;
it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') { if (it != end && *it == '?') {
debug_ = true; debug_ = true;
++it; ++it;
@@ -145,8 +148,8 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
!path_type_ ? p.native() !path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>(); : p.generic_string<std::filesystem::path::value_type>();
detail::handle_dynamic_spec<detail::width_checker>(specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
if (!debug_) { if (!debug_) {
auto s = detail::get_path_string<Char>(p, path_string); auto s = detail::get_path_string<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs); return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
@@ -178,9 +181,9 @@ FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char> template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> { struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private: private:
// Functor because C++11 doesn't support generic lambdas. // Functor because C++11 doesn't support generic lambdas.
struct writer { struct writer {
@@ -200,18 +203,16 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return write_padded(ctx, writer{bs}); return this->write_padded(ctx, writer{bs});
} }
}; };
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE 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>> {
@@ -233,7 +234,7 @@ struct formatter<std::optional<T>, Char,
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public: public:
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
maybe_set_debug_format(underlying_, true); maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx); return underlying_.parse(ctx);
} }
@@ -274,13 +275,12 @@ FMT_END_NAMESPACE
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE 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<is_formattable<T, Char>::value && std::enable_if_t<(std::is_void<T>::value ||
is_formattable<T, Char>::value) &&
is_formattable<E, Char>::value>> { is_formattable<E, Char>::value>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@@ -291,7 +291,8 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) { if (value.has_value()) {
out = detail::write<Char>(out, "expected("); out = detail::write<Char>(out, "expected(");
out = detail::write_escaped_alternative<Char>(out, *value); if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value);
} else { } else {
out = detail::write<Char>(out, "unexpected("); out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error()); out = detail::write_escaped_alternative<Char>(out, value.error());
@@ -305,11 +306,8 @@ FMT_END_NAMESPACE
#ifdef __cpp_lib_source_location #ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> { template <> struct formatter<std::source_location> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) { FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
return ctx.begin();
}
template <typename FormatContext> template <typename FormatContext>
auto format(const std::source_location& loc, FormatContext& ctx) const auto format(const std::source_location& loc, FormatContext& ctx) const
@@ -363,10 +361,8 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value; detail::is_variant_formattable_<T, C>::value;
}; };
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> { template <typename Char> struct formatter<std::monostate, Char> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@@ -377,14 +373,12 @@ 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_formattable<Variant, Char>>>> { is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
template <typename ParseContext> FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@@ -412,21 +406,55 @@ FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT template <> struct formatter<std::error_code> {
template <typename Char> struct formatter<std::error_code, Char> { private:
template <typename ParseContext> format_specs specs_;
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { detail::arg_ref<char> width_ref_;
return ctx.begin(); bool debug_ = false;
public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
char c = *it;
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
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;
} }
template <typename FormatContext> template <typename FormatContext>
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const FMT_CONSTEXPR20 auto format(const std::error_code& ec,
-> decltype(ctx.out()) { FormatContext& ctx) const -> decltype(ctx.out()) {
auto out = ctx.out(); auto specs = specs_;
out = detail::write_bytes<Char>(out, ec.category().name(), format_specs()); detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
out = detail::write<Char>(out, Char(':')); ctx);
out = detail::write<Char>(out, ec.value()); auto buf = memory_buffer();
return out; if (specs_.type() == presentation_type::string) {
buf.append(ec.message());
} else {
buf.append(string_view(ec.category().name()));
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);
} }
}; };
@@ -501,13 +529,11 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types. struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> { > {
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
-> decltype(ctx.begin()) {
return ctx.begin(); return ctx.begin();
} }
@@ -519,7 +545,6 @@ struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
}; };
#endif #endif
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter< struct formatter<
T, Char, // DEPRECATED! Mixing code unit types. T, Char, // DEPRECATED! Mixing code unit types.
@@ -528,8 +553,7 @@ struct formatter<
bool with_typename_ = false; bool with_typename_ = false;
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
-> decltype(ctx.begin()) {
auto it = ctx.begin(); auto it = ctx.begin();
auto end = ctx.end(); auto end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@@ -586,7 +610,6 @@ struct is_bit_reference_like<std::__bit_const_reference<C>> {
// 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>>
@@ -606,7 +629,6 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get(); 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>>
@@ -619,7 +641,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>
@@ -630,7 +651,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_;
@@ -643,7 +663,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
if (c.real() != 0) { if (c.real() != 0) {
*out++ = Char('('); *out++ = Char('(');
out = detail::write<Char>(out, c.real(), specs, ctx.locale()); out = detail::write<Char>(out, c.real(), specs, ctx.locale());
specs.sign = sign::plus; specs.set_sign(sign::plus);
out = detail::write<Char>(out, c.imag(), specs, ctx.locale()); out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
if (!detail::isfinite(c.imag())) *out++ = Char(' '); if (!detail::isfinite(c.imag())) *out++ = Char(' ');
*out++ = Char('i'); *out++ = Char('i');
@@ -657,8 +677,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
public: public:
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
-> decltype(ctx.begin()) {
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type_constant<T, Char>::value); detail::type_constant<T, Char>::value);
@@ -668,12 +687,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto format(const std::complex<T>& c, FormatContext& ctx) const auto format(const std::complex<T>& c, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto specs = specs_; auto specs = specs_;
if (specs.width_ref.kind != detail::arg_id_kind::none || if (specs.dynamic()) {
specs.precision_ref.kind != detail::arg_id_kind::none) { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
detail::handle_dynamic_spec<detail::width_checker>(specs.width, specs.width_ref, ctx);
specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
detail::handle_dynamic_spec<detail::precision_checker>( specs.precision_ref, ctx);
specs.precision, specs.precision_ref, ctx);
} }
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
@@ -681,12 +699,12 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs(); auto outer_specs = format_specs();
outer_specs.width = specs.width; outer_specs.width = specs.width;
outer_specs.fill = specs.fill; outer_specs.copy_fill_from(specs);
outer_specs.align = specs.align; outer_specs.set_align(specs.align());
specs.width = 0; specs.width = 0;
specs.fill = {}; specs.set_fill({});
specs.align = align::none; specs.set_align(align::none);
do_format(c, specs, ctx, basic_appender<Char>(buf)); do_format(c, specs, ctx, basic_appender<Char>(buf));
return detail::write<Char>(ctx.out(), return detail::write<Char>(ctx.out(),
@@ -695,5 +713,16 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
}; };
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
-> decltype(ctx.out()) {
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
}
};
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_STD_H_ #endif // FMT_STD_H_

View File

@@ -10,11 +10,12 @@
#include "color.h" #include "color.h"
#include "format.h" #include "format.h"
#include "ostream.h"
#include "ranges.h" #include "ranges.h"
#ifndef FMT_MODULE #ifndef FMT_MODULE
# include <cwchar> # include <cwchar>
# if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) # if FMT_USE_LOCALE
# include <locale> # include <locale>
# endif # endif
#endif #endif
@@ -34,7 +35,8 @@ struct format_string_char<
}; };
template <typename S> template <typename S>
struct format_string_char<S, enable_if_t<is_compile_string<S>::value>> { struct format_string_char<
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
using type = typename S::char_type; using type = typename S::char_type;
}; };
@@ -43,7 +45,7 @@ using format_string_char_t = typename format_string_char<S>::type;
inline auto write_loc(basic_appender<wchar_t> out, loc_value value, inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
const format_specs& specs, locale_ref loc) -> bool { const format_specs& specs, locale_ref loc) -> bool {
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #if FMT_USE_LOCALE
auto& numpunct = auto& numpunct =
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>()); std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
auto separator = std::wstring(); auto separator = std::wstring();
@@ -58,30 +60,60 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
using wstring_view = basic_string_view<wchar_t>; using wstring_view = basic_string_view<wchar_t>;
using wformat_parse_context = basic_format_parse_context<wchar_t>; using wformat_parse_context = parse_context<wchar_t>;
using wformat_context = buffered_context<wchar_t>; using wformat_context = buffered_context<wchar_t>;
using wformat_args = basic_format_args<wformat_context>; using wformat_args = basic_format_args<wformat_context>;
using wmemory_buffer = basic_memory_buffer<wchar_t>; using wmemory_buffer = basic_memory_buffer<wchar_t>;
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 template <typename Char, typename... T> struct basic_fstring {
// Workaround broken conversion on older gcc. private:
template <typename... Args> using wformat_string = wstring_view; basic_string_view<Char> str_;
inline auto runtime(wstring_view s) -> wstring_view { return s; }
#else static constexpr int num_static_named_args =
template <typename... Args> detail::count_static_named_args<T...>();
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
using checker = detail::format_string_checker<
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
num_static_named_args != detail::count_named_args<T...>()>;
using arg_pack = detail::arg_pack<T...>;
public:
using t = basic_fstring;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
if (FMT_USE_CONSTEVAL)
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
}
template <typename S,
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
std::is_same<typename S::char_type, Char>::value)>
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
FMT_CONSTEXPR int ignore =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
}
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
operator basic_string_view<Char>() const { return str_; }
auto get() const -> basic_string_view<Char> { return str_; }
};
template <typename Char, typename... T>
using basic_format_string = basic_fstring<Char, T...>;
template <typename... T>
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> { inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
#endif
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 #ifdef __cpp_char8_t
template <> template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled()> {};
#endif #endif
template <typename... T> template <typename... T>
@@ -90,14 +122,13 @@ constexpr auto make_wformat_args(T&... args)
return fmt::make_format_args<wformat_context>(args...); return fmt::make_format_args<wformat_context>(args...);
} }
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals { inline namespace literals {
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_ARGS inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
constexpr auto operator""_a(const wchar_t* s, size_t)
-> detail::udl_arg<wchar_t> {
return {s}; return {s};
} }
#endif
} // namespace literals } // namespace literals
#endif
template <typename It, typename Sentinel> template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, wstring_view sep) auto join(It begin, Sentinel end, wstring_view sep)
@@ -105,9 +136,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep}; return {begin, end, sep};
} }
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep) auto join(Range&& range, wstring_view sep)
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>, -> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> { wchar_t> {
return join(std::begin(range), std::end(range), sep); return join(std::begin(range), std::end(range), sep);
} }
@@ -118,19 +149,19 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> { -> tuple_join_view<wchar_t, Tuple> {
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> format_str, auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args) 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, format_str, args); detail::vformat_to(buf, fmt, args);
return to_string(buf); return {buf.data(), buf.size()};
} }
template <typename... T> template <typename... T>
@@ -151,8 +182,8 @@ template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(!std::is_same<Char, char>::value && FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
!std::is_same<Char, wchar_t>::value)> !std::is_same<Char, wchar_t>::value)>
auto format(const S& format_str, T&&... args) -> std::basic_string<Char> { auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
return vformat(detail::to_string_view(format_str), return vformat(detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
@@ -160,31 +191,33 @@ template <typename Locale, typename S,
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_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& format_str, inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args) typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return detail::vformat(loc, detail::to_string_view(format_str), args); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args,
detail::locale_ref(loc));
return {buf.data(), buf.size()};
} }
template <typename Locale, typename S, typename... T, template <typename Locale, 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_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& format_str, T&&... args) inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return detail::vformat( return vformat(loc, detail::to_string_view(fmt),
loc, detail::to_string_view(format_str), fmt::make_format_args<buffered_context<Char>>(args...));
fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename OutputIt, typename S, template <typename OutputIt, typename S,
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_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& format_str, auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt { typename detail::vformat_args<Char>::type args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(format_str), args); detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
@@ -203,37 +236,35 @@ template <typename Locale, typename S, typename OutputIt, typename... Args,
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_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, const Locale& loc, const S& format_str, inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type 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(format_str), args, vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc));
detail::locale_ref(loc));
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename Locale, typename S, typename... T, template <typename Locale, 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_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& format_str, inline auto format_to(OutputIt out, const Locale& 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(format_str), 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...));
} }
template <typename OutputIt, typename Char, typename... Args, 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, inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
basic_string_view<Char> format_str,
typename detail::vformat_args<Char>::type args) typename detail::vformat_args<Char>::type 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);
detail::vformat_to(buf, format_str, args); detail::vformat_to(buf, fmt, args);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
@@ -287,31 +318,47 @@ 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);
return fmt::to_string(buf); return {buf.data(), buf.size()};
} }
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> template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt,
wformat_string<T...> fmt, const T&... args) { const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...)); vprint(f, ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T> template <typename... T>
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt, FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt,
const T&... args) { const T&... args) {
return print(stdout, ts, fmt, args...); return print(stdout, ts, fmt, args...);
} }
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args);
detail::write_buffer(os, buffer);
}
template <typename... T>
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
}
template <typename... T>
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
/// Converts `value` to `std::wstring` using the default format for type `T`. /// Converts `value` to `std::wstring` using the default format for type `T`.
template <typename T> inline auto to_wstring(const T& value) -> std::wstring { template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
return format(FMT_STRING(L"{}"), value); return format(FMT_STRING(L"{}"), value);

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

@@ -20,7 +20,7 @@ SPDLOG_INLINE ansicolor_sink<ConsoleMutex>::ansicolor_sink(FILE *target_file, co
formatter_(details::make_unique<spdlog::pattern_formatter>()) formatter_(details::make_unique<spdlog::pattern_formatter>())
{ {
set_color_mode(mode); set_color_mode_(mode);
colors_.at(level::trace) = to_string_(white); colors_.at(level::trace) = to_string_(white);
colors_.at(level::debug) = to_string_(cyan); colors_.at(level::debug) = to_string_(cyan);
colors_.at(level::info) = to_string_(green); colors_.at(level::info) = to_string_(green);
@@ -82,12 +82,18 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_formatter(
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() { SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() const {
return should_do_colors_; return should_do_colors_;
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) { SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) {
std::lock_guard<mutex_t> lock(mutex_);
set_color_mode_(mode);
}
template <typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode_(color_mode mode) {
switch (mode) { switch (mode) {
case color_mode::always: case color_mode::always:
should_do_colors_ = true; should_do_colors_ = true;
@@ -105,15 +111,16 @@ 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) { SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(
fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); const string_view_t &color_code) const {
details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_);
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted,
size_t start, size_t start,
size_t end) { size_t end) const {
fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); details::os::fwrite_bytes(formatted.data() + start, end - start, target_file_);
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>

View File

@@ -36,11 +36,11 @@ public:
void set_color(level::level_enum color_level, string_view_t color); void set_color(level::level_enum color_level, string_view_t color);
void set_color_mode(color_mode mode); void set_color_mode(color_mode mode);
bool should_color(); bool should_color() const;
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
@@ -84,8 +84,9 @@ private:
bool should_do_colors_; bool should_do_colors_;
std::unique_ptr<spdlog::formatter> formatter_; std::unique_ptr<spdlog::formatter> formatter_;
std::array<std::string, level::n_levels> colors_; std::array<std::string, level::n_levels> colors_;
void print_ccode_(const string_view_t &color_code); void set_color_mode_(color_mode mode);
void print_range_(const memory_buf_t &formatted, size_t start, size_t end); void print_ccode_(const string_view_t &color_code) const;
void print_range_(const memory_buf_t &formatted, size_t start, size_t end) const;
static std::string to_string_(const string_view_t &sv); static std::string to_string_(const string_view_t &sv);
}; };

View File

@@ -26,6 +26,12 @@ SPDLOG_INLINE const filename_t &basic_file_sink<Mutex>::filename() const {
return file_helper_.filename(); return file_helper_.filename();
} }
template <typename Mutex>
SPDLOG_INLINE void basic_file_sink<Mutex>::truncate() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
file_helper_.reopen(true);
}
template <typename Mutex> template <typename Mutex>
SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) { SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) {
memory_buf_t formatted; memory_buf_t formatted;

View File

@@ -23,6 +23,7 @@ public:
bool truncate = false, bool truncate = false,
const file_event_handlers &event_handlers = {}); const file_event_handlers &event_handlers = {});
const filename_t &filename() const; const filename_t &filename() const;
void truncate();
protected: protected:
void sink_it_(const details::log_msg &msg) override; void sink_it_(const details::log_msg &msg) override;

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

@@ -62,7 +62,6 @@ struct daily_filename_format_calculator {
* Rotating file sink based on date. * Rotating file sink based on date.
* If truncate != false , the created file will be truncated. * If truncate != false , the created file will be truncated.
* If max_files > 0, retain only the last max_files and delete previous. * If max_files > 0, retain only the last max_files and delete previous.
* If max_files > 0, retain only the last max_files and delete previous.
* Note that old log files from previous executions will not be deleted by this class, * Note that old log files from previous executions will not be deleted by this class,
* rotation and deletion is only applied while the program is running. * rotation and deletion is only applied while the program is running.
*/ */

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

@@ -13,7 +13,7 @@ namespace spdlog {
namespace sinks { namespace sinks {
template <typename Mutex> template <typename Mutex>
class null_sink : public base_sink<Mutex> { class null_sink final : public base_sink<Mutex> {
protected: protected:
void sink_it_(const details::log_msg &) override {} void sink_it_(const details::log_msg &) override {}
void flush_() override {} void flush_() override {}

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);
} }
@@ -69,6 +69,41 @@ SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::filename() {
return file_helper_.filename(); return file_helper_.filename();
} }
template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_now() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
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) {
memory_buf_t formatted; memory_buf_t formatted;

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,
@@ -28,6 +28,11 @@ public:
const file_event_handlers &event_handlers = {}); const file_event_handlers &event_handlers = {});
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 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;
@@ -41,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);
@@ -60,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

@@ -10,6 +10,7 @@
#include <memory> #include <memory>
#include <spdlog/details/console_globals.h> #include <spdlog/details/console_globals.h>
#include <spdlog/pattern_formatter.h> #include <spdlog/pattern_formatter.h>
#include <spdlog/details/os.h>
#ifdef _WIN32 #ifdef _WIN32
// under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675) // under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675)
@@ -22,7 +23,7 @@
#include <io.h> // _get_osfhandle(..) #include <io.h> // _get_osfhandle(..)
#include <stdio.h> // _fileno(..) #include <stdio.h> // _fileno(..)
#endif // WIN32 #endif // _WIN32
namespace spdlog { namespace spdlog {
@@ -44,7 +45,7 @@ SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file)
if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr) { if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr) {
throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() failed", errno); throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() failed", errno);
} }
#endif // WIN32 #endif // _WIN32
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
@@ -67,8 +68,8 @@ SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &m
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
memory_buf_t formatted; memory_buf_t formatted;
formatter_->format(msg, formatted); formatter_->format(msg, formatted);
::fwrite(formatted.data(), sizeof(char), formatted.size(), file_); details::os::fwrite_bytes(formatted.data(), formatted.size(), file_);
#endif // WIN32 #endif // _WIN32
::fflush(file_); // flush every line to terminal ::fflush(file_); // flush every line to terminal
} }

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

@@ -104,6 +104,13 @@
// //
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY // #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY
// CRITICAL", "OFF" } // CRITICAL", "OFF" }
//
// For C++17 use string_view_literals:
//
// #include <string_view>
// 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 CRITICAL"sv, "OFF"sv }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@@ -5,7 +5,7 @@
#define SPDLOG_VER_MAJOR 1 #define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 15 #define SPDLOG_VER_MINOR 15
#define SPDLOG_VER_PATCH 0 #define SPDLOG_VER_PATCH 3
#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

@@ -13,31 +13,32 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept template FMT_API auto dragonbox::to_decimal(float x) noexcept -> dragonbox::decimal_fp<float>;
-> dragonbox::decimal_fp<float>; template FMT_API auto dragonbox::to_decimal(double x) noexcept -> dragonbox::decimal_fp<double>;
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR #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 locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale; template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif #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) -> thousands_sep_result<char>;
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char; template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
template FMT_API void vformat_to(buffer<char>&, string_view, // DEPRECATED!
typename vformat_args<>::type, locale_ref); 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;
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*);

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()
@@ -48,7 +48,9 @@ set(SPDLOG_UTESTS_SOURCES
test_cfg.cpp test_cfg.cpp
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_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)
@@ -58,10 +60,6 @@ if(systemd_FOUND)
list(APPEND SPDLOG_UTESTS_SOURCES test_systemd.cpp) list(APPEND SPDLOG_UTESTS_SOURCES test_systemd.cpp)
endif() endif()
if(NOT SPDLOG_USE_STD_FORMAT)
list(APPEND SPDLOG_UTESTS_SOURCES test_bin_to_hex.cpp)
endif()
enable_testing() enable_testing()
function(spdlog_prepare_test test_target spdlog_lib) function(spdlog_prepare_test test_target spdlog_lib)
@@ -74,9 +72,9 @@ 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})
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON) set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction() endfunction()

View File

@@ -19,6 +19,15 @@ TEST_CASE("env", "[cfg]") {
#endif #endif
load_env_levels(); load_env_levels();
REQUIRE(l1->level() == spdlog::level::warn); REQUIRE(l1->level() == spdlog::level::warn);
#ifdef CATCH_PLATFORM_WINDOWS
_putenv_s("MYAPP_LEVEL", "l1=trace");
#else
setenv("MYAPP_LEVEL", "l1=trace", 1);
#endif
load_env_levels("MYAPP_LEVEL");
REQUIRE(l1->level() == spdlog::level::trace);
spdlog::set_default_logger(spdlog::create<test_sink_st>("cfg-default")); spdlog::set_default_logger(spdlog::create<test_sink_st>("cfg-default"));
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
} }

View File

@@ -16,7 +16,9 @@ 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);
lines.emplace_back(formatted.begin(), formatted.end() - eol_len); 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));
}); });
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,12 +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) {
filename_memory_buf_t w; return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename,
spdlog::fmt_lib::format_to(std::back_inserter(w), 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);
return SPDLOG_BUF_TO_STRING(w);
} }
}; };

View File

@@ -45,6 +45,26 @@ TEST_CASE("flush_on", "[flush_on]") {
default_eol, default_eol, default_eol)); default_eol, default_eol, default_eol));
} }
TEST_CASE("simple_file_logger", "[truncate]") {
prepare_logdir();
const spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
const bool truncate = true;
const auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, truncate);
const auto logger = std::make_shared<spdlog::logger>("simple_file_logger", sink);
logger->info("Test message {}", 3.14);
logger->info("Test message {}", 2.71);
logger->flush();
REQUIRE(count_lines(SIMPLE_LOG) == 2);
sink->truncate();
REQUIRE(count_lines(SIMPLE_LOG) == 0);
logger->info("Test message {}", 6.28);
logger->flush();
REQUIRE(count_lines(SIMPLE_LOG) == 1);
}
TEST_CASE("rotating_file_logger1", "[rotating_logger]") { TEST_CASE("rotating_file_logger1", "[rotating_logger]") {
prepare_logdir(); prepare_logdir();
size_t max_size = 1024 * 10; size_t max_size = 1024 * 10;
@@ -74,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++) {
@@ -101,3 +118,70 @@ TEST_CASE("rotating_file_logger3", "[rotating_logger]") {
REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0),
spdlog::spdlog_ex); spdlog::spdlog_ex);
} }
// test on-demand rotation of logs
TEST_CASE("rotating_file_logger4", "[rotating_logger]") {
prepare_logdir();
size_t max_size = 1024 * 10;
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_st>(basename, max_size, 2);
auto logger = std::make_shared<spdlog::logger>("rotating_sink_logger", sink);
logger->info("Test message - pre-rotation");
logger->flush();
sink->rotate_now();
logger->info("Test message - post-rotation");
logger->flush();
REQUIRE(get_filesize(ROTATING_LOG) > 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,8 +1,12 @@
#ifdef _WIN32 // to prevent fopen warning on windows
#define _CRT_SECURE_NO_WARNINGS
#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);
@@ -178,10 +182,43 @@ 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 {
FILE* fp = nullptr;
explicit auto_closer(FILE* f)
: fp(f) {}
auto_closer(const auto_closer&) = delete;
auto_closer& operator=(const auto_closer&) = delete;
~auto_closer() {
if (fp != nullptr) (void)std::fclose(fp);
}
};
TEST_CASE("os::fwrite_bytes", "[os]") {
using spdlog::details::os::create_dir;
using spdlog::details::os::fwrite_bytes;
const char* filename = "log_tests/test_fwrite_bytes.txt";
const char* msg = "hello";
prepare_logdir();
REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true);
{
auto_closer closer(std::fopen(filename, "wb"));
REQUIRE(closer.fp != nullptr);
REQUIRE(fwrite_bytes(msg, std::strlen(msg), closer.fp) == true);
REQUIRE(fwrite_bytes(msg, 0, closer.fp) == true);
std::fflush(closer.fp);
REQUIRE(spdlog::details::os::filesize(closer.fp) == 5);
}
// fwrite_bytes should return false on write failure
auto_closer closer(std::fopen(filename, "r"));
REQUIRE(closer.fp != nullptr);
REQUIRE_FALSE(fwrite_bytes("Hello", 5, closer.fp));
}

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,

50
tests/test_ringbuffer.cpp Normal file
View File

@@ -0,0 +1,50 @@
#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);
REQUIRE(formatted[0] == "msg1");
REQUIRE(formatted[1] == "msg2");
REQUIRE(formatted[2] == "msg3");
}
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);
REQUIRE(formatted[0] == "second");
REQUIRE(formatted[1] == "third");
}
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);
REQUIRE(formatted[0] == "B");
REQUIRE(formatted[1] == "C");
}

View File

@@ -47,8 +47,9 @@ 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;
if (lines_.size() < lines_to_save) { if (lines_.size() < lines_to_save) {
lines_.emplace_back(formatted.begin(), formatted.end() - eol_len); lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len));
} }
msg_counter_++; msg_counter_++;
std::this_thread::sleep_for(delay_); std::this_thread::sleep_for(delay_);

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());
} }