mirror of
https://github.com/gabime/spdlog.git
synced 2026-04-10 11:34:29 +08:00
Compare commits
318 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61ed2a670e | ||
|
|
db1bc035f7 | ||
|
|
8de6cdaa82 | ||
|
|
fe1a4f5fb6 | ||
|
|
d38f89cae8 | ||
|
|
9c90fe8264 | ||
|
|
b85a666f72 | ||
|
|
5ba95f6816 | ||
|
|
dc38b7c3c4 | ||
|
|
6484b03dd9 | ||
|
|
29235d9b4b | ||
|
|
4b3687f1a6 | ||
|
|
e7e8b75a4c | ||
|
|
e98265a49b | ||
|
|
e87f69bdb6 | ||
|
|
70d2832c0d | ||
|
|
7636f1f659 | ||
|
|
1523c83650 | ||
|
|
a6987efaec | ||
|
|
6491abb519 | ||
|
|
8faabb4e3a | ||
|
|
2838c2c8a5 | ||
|
|
3315bad009 | ||
|
|
3eeced78b5 | ||
|
|
c23430b438 | ||
|
|
0e49bfff51 | ||
|
|
3ed40d04a9 | ||
|
|
70b36aa55d | ||
|
|
0f83b33d4f | ||
|
|
b83106bed4 | ||
|
|
21413e599a | ||
|
|
5f4cc7b036 | ||
|
|
9aa26fb969 | ||
|
|
7f74012a0d | ||
|
|
96ebef093f | ||
|
|
a19f4bba0c | ||
|
|
c24b957e17 | ||
|
|
5ba2f77230 | ||
|
|
a09f490804 | ||
|
|
082d6fbea9 | ||
|
|
37372960a8 | ||
|
|
0035a0c98d | ||
|
|
036cc5d575 | ||
|
|
14950926ed | ||
|
|
baa3b1a07e | ||
|
|
2a09f66a44 | ||
|
|
e50b62c770 | ||
|
|
13d8b0f17f | ||
|
|
9e0c658b29 | ||
|
|
74fec56927 | ||
|
|
514f304a47 | ||
|
|
7f85a5c988 | ||
|
|
14d626d961 | ||
|
|
7560cacb3f | ||
|
|
3cd9bcdab9 | ||
|
|
32f1efdc99 | ||
|
|
bcc9f03457 | ||
|
|
4c845bf02b | ||
|
|
c727864393 | ||
|
|
4548573a75 | ||
|
|
385246730d | ||
|
|
e06d21a4c0 | ||
|
|
c132d2ae8c | ||
|
|
ece31100e0 | ||
|
|
ce4e1ac54b | ||
|
|
ef61fb11f0 | ||
|
|
42f2b11ec8 | ||
|
|
ac87cbb0d1 | ||
|
|
c65de3d689 | ||
|
|
1433fa4209 | ||
|
|
d51149e5ac | ||
|
|
ffd813435a | ||
|
|
d75fd2c7f9 | ||
|
|
cdad84aa46 | ||
|
|
0fdb545d8c | ||
|
|
a5f5ff70e0 | ||
|
|
4f0e320236 | ||
|
|
68aed6a5eb | ||
|
|
6811112208 | ||
|
|
9ebc4b24d9 | ||
|
|
b990080a52 | ||
|
|
efbe3e4d57 | ||
|
|
7b14a65b2b | ||
|
|
5887744d8b | ||
|
|
8bf718671a | ||
|
|
c858b14c03 | ||
|
|
12df172575 | ||
|
|
7fa751d36e | ||
|
|
7a7611e977 | ||
|
|
ec8763adf2 | ||
|
|
f2d1d573f5 | ||
|
|
a530b87fd0 | ||
|
|
6c21789aed | ||
|
|
616866fcf4 | ||
|
|
faf06bcfe5 | ||
|
|
cd376a5c43 | ||
|
|
6ba5ab6d67 | ||
|
|
1bee3218b4 | ||
|
|
802eaadd2d | ||
|
|
ee22eed23d | ||
|
|
ab72de5f7a | ||
|
|
a32cea24fd | ||
|
|
af0d805be4 | ||
|
|
181c22f798 | ||
|
|
87133ef6b7 | ||
|
|
1ef2f014ee | ||
|
|
0a92d1d684 | ||
|
|
ff5221b693 | ||
|
|
db484cc4b8 | ||
|
|
6442963f49 | ||
|
|
0f7b95ce47 | ||
|
|
632a2e0894 | ||
|
|
e9635c7b2d | ||
|
|
8e3b1338a5 | ||
|
|
9d3dde0900 | ||
|
|
c5abaeddca | ||
|
|
ca2cd6f3e7 | ||
|
|
7d07e0312a | ||
|
|
e1c73fd8f4 | ||
|
|
b83ab21283 | ||
|
|
8001156ca8 | ||
|
|
57e31f0a58 | ||
|
|
51fadf6b7e | ||
|
|
2a6a8aa0a0 | ||
|
|
aa264a7fb2 | ||
|
|
5e35c2b6ab | ||
|
|
0385372314 | ||
|
|
efbff95ec7 | ||
|
|
2a9edb2153 | ||
|
|
be14e60d9e | ||
|
|
ef4641cad7 | ||
|
|
100f30043f | ||
|
|
1574b5b0a2 | ||
|
|
012fe99ab1 | ||
|
|
8ff5a3e096 | ||
|
|
65317eb019 | ||
|
|
ac3e26b0ff | ||
|
|
e86f450428 | ||
|
|
7b2776fdc7 | ||
|
|
2a16d1d230 | ||
|
|
53e1c9ab11 | ||
|
|
410abc4626 | ||
|
|
a2e28443f0 | ||
|
|
c1af0a3f21 | ||
|
|
bb5e1ee2f9 | ||
|
|
3aee89c8fd | ||
|
|
44e1f9f682 | ||
|
|
37d76b961c | ||
|
|
1305663d99 | ||
|
|
8f4efe57a2 | ||
|
|
0613dbc4a2 | ||
|
|
0ed0d69368 | ||
|
|
2ffbbee1f6 | ||
|
|
b9d2f2537b | ||
|
|
69dc173979 | ||
|
|
ded8b5ebd4 | ||
|
|
ed58ae9f98 | ||
|
|
f7f790b4b3 | ||
|
|
fe74c80992 | ||
|
|
fa659bf7ad | ||
|
|
9b41649601 | ||
|
|
1b3438f5a5 | ||
|
|
3eed64e5c4 | ||
|
|
0fac33781d | ||
|
|
3135b6a33d | ||
|
|
2686ae2322 | ||
|
|
a709e29586 | ||
|
|
dd46579cb4 | ||
|
|
f4b7210e7b | ||
|
|
05a0b0d7b0 | ||
|
|
c1f4d7506a | ||
|
|
b6ba0be550 | ||
|
|
23dfb4e2f9 | ||
|
|
7a10e31982 | ||
|
|
de89c4fd01 | ||
|
|
5d4956d34b | ||
|
|
42c5eb59c9 | ||
|
|
09cc6e7754 | ||
|
|
4a5bc41e89 | ||
|
|
0ade18828d | ||
|
|
91046e6ca4 | ||
|
|
17e1ba8ae2 | ||
|
|
c47c854f15 | ||
|
|
e931866b35 | ||
|
|
7828a065bf | ||
|
|
3e689e700e | ||
|
|
a9964afcf7 | ||
|
|
95c19876c6 | ||
|
|
5efccfa5e2 | ||
|
|
89e737a258 | ||
|
|
8fbc853b0d | ||
|
|
2e008b319c | ||
|
|
ff6e3c95f2 | ||
|
|
7e9385405f | ||
|
|
592ea36a86 | ||
|
|
e059ebf99d | ||
|
|
609480ed78 | ||
|
|
4271185936 | ||
|
|
aacae62591 | ||
|
|
47cbf3828d | ||
|
|
46d418164d | ||
|
|
ede8d84884 | ||
|
|
53d223b45f | ||
|
|
ac35dd5a6f | ||
|
|
9e19012cb0 | ||
|
|
1234cda3b3 | ||
|
|
710a0e3a45 | ||
|
|
b7f24b2456 | ||
|
|
fc594b551a | ||
|
|
f39ccccc0c | ||
|
|
f0a4ddd78b | ||
|
|
c691769e46 | ||
|
|
19dc30567e | ||
|
|
a453bccff0 | ||
|
|
aa2053a575 | ||
|
|
3d8f71c4d2 | ||
|
|
6aaaabbc4d | ||
|
|
42c36f48ed | ||
|
|
a5f4139102 | ||
|
|
030d85a9b3 | ||
|
|
adcfb7fb55 | ||
|
|
cec365888a | ||
|
|
55bfa8dd11 | ||
|
|
e99759fe45 | ||
|
|
17c6e6ee3f | ||
|
|
7fff900a1a | ||
|
|
c67974e4c8 | ||
|
|
a36696e02e | ||
|
|
9b80ca6c41 | ||
|
|
22f514aabf | ||
|
|
211478e13e | ||
|
|
5e33a7e58b | ||
|
|
b2e31721e8 | ||
|
|
de0dbfa359 | ||
|
|
f93459579f | ||
|
|
2b81c40b90 | ||
|
|
233e97c5e4 | ||
|
|
fc1ce48dc7 | ||
|
|
fd5562eebe | ||
|
|
0695d9cb5f | ||
|
|
456b24134d | ||
|
|
f8ba24afee | ||
|
|
eebb921c9f | ||
|
|
e17ee87f38 | ||
|
|
18e3f07f7d | ||
|
|
9ce39a470f | ||
|
|
23572369fc | ||
|
|
01b350de96 | ||
|
|
365e470a32 | ||
|
|
a42b40656c | ||
|
|
40160f2a57 | ||
|
|
90b33b1552 | ||
|
|
5567ed01e5 | ||
|
|
cbe9448650 | ||
|
|
5b345534dc | ||
|
|
c8dc318fb3 | ||
|
|
23cb1a1080 | ||
|
|
21cf8d7d3c | ||
|
|
3cf4d34094 | ||
|
|
16d78ae5db | ||
|
|
62b4b7af83 | ||
|
|
9799ecac6a | ||
|
|
dccb766095 | ||
|
|
c97983a91c | ||
|
|
680fb07fd5 | ||
|
|
cfd0ea197c | ||
|
|
48d4ed9bc0 | ||
|
|
3bed78356e | ||
|
|
8923922f30 | ||
|
|
7542e42e4f | ||
|
|
7a9b23e4f4 | ||
|
|
47253ba2a1 | ||
|
|
69b54dd9e4 | ||
|
|
36138617fc | ||
|
|
231ca50700 | ||
|
|
c7613f3e91 | ||
|
|
05d5546eb1 | ||
|
|
cefe67726e | ||
|
|
1ac2dcc537 | ||
|
|
3a68eecb28 | ||
|
|
54a8259b42 | ||
|
|
32b6f1619f | ||
|
|
99b8c5d379 | ||
|
|
5deb7c55e1 | ||
|
|
9cd25dd216 | ||
|
|
4a9ccf7e38 | ||
|
|
2963b9f07f | ||
|
|
a4a9bc4d8e | ||
|
|
a16a029790 | ||
|
|
97fea81599 | ||
|
|
ccffb6ecd6 | ||
|
|
63b5a1a4d8 | ||
|
|
dfc777803a | ||
|
|
934a9bb23e | ||
|
|
7097f7a894 | ||
|
|
537fd7c4ba | ||
|
|
d5048b8b0c | ||
|
|
0348556aac | ||
|
|
d6329b9dce | ||
|
|
34f3d29d93 | ||
|
|
cd701761f9 | ||
|
|
23c2c00d69 | ||
|
|
fa501b46cf | ||
|
|
38dc0a5c5d | ||
|
|
685cc4edbc | ||
|
|
78369375e3 | ||
|
|
6587058f74 | ||
|
|
30f738e49a | ||
|
|
726ca01e5c | ||
|
|
83b40b8cda | ||
|
|
db0d0438ff | ||
|
|
3c527488e7 | ||
|
|
99abcf6ded | ||
|
|
7009727559 | ||
|
|
ae02fba141 | ||
|
|
76cdeb62e3 | ||
|
|
ae9627c64c | ||
|
|
58875bdcd7 |
@@ -104,5 +104,6 @@ SpacesInSquareBrackets: false
|
|||||||
Standard: Cpp11
|
Standard: Cpp11
|
||||||
TabWidth: 8
|
TabWidth: 8
|
||||||
UseTab: Never
|
UseTab: Never
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Checks: 'cppcoreguidelines-*,
|
|||||||
performance-*,
|
performance-*,
|
||||||
modernize-*,
|
modernize-*,
|
||||||
google-*,
|
google-*,
|
||||||
misc-*,
|
misc-*
|
||||||
cert-*,
|
cert-*,
|
||||||
readability-*,
|
readability-*,
|
||||||
clang-analyzer-*,
|
clang-analyzer-*,
|
||||||
|
|||||||
42
.travis.yml
42
.travis.yml
@@ -5,7 +5,7 @@
|
|||||||
sudo: required
|
sudo: required
|
||||||
language: cpp
|
language: cpp
|
||||||
|
|
||||||
# gcc 4.8
|
# gcc t
|
||||||
addons: &gcc48
|
addons: &gcc48
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
@@ -21,6 +21,16 @@ addons: &gcc7
|
|||||||
sources:
|
sources:
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
|
|
||||||
|
|
||||||
|
# gcc 9.0
|
||||||
|
addons: &gcc9
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- g++-9
|
||||||
|
sources:
|
||||||
|
- ubuntu-toolchain-r-test
|
||||||
|
|
||||||
|
|
||||||
# Clang 3.5
|
# Clang 3.5
|
||||||
addons: &clang35
|
addons: &clang35
|
||||||
apt:
|
apt:
|
||||||
@@ -44,43 +54,43 @@ addons: &clang10
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Test gcc-4.8: C++11, Build=Debug/Release
|
# Test gcc-4.8: C++11, Build=Release
|
||||||
- env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11
|
|
||||||
os: linux
|
|
||||||
addons: *gcc48
|
|
||||||
|
|
||||||
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11
|
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11
|
||||||
os: linux
|
os: linux
|
||||||
addons: *gcc48
|
addons: *gcc48
|
||||||
|
|
||||||
|
# Test gcc-7: C++11, Build=Release
|
||||||
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
|
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
|
||||||
os: linux
|
os: linux
|
||||||
addons: *gcc7
|
addons: *gcc7
|
||||||
|
|
||||||
# Test clang-3.5: C++11, Build=Debug/Release
|
# Test gcc-9: C++17, Build=Release
|
||||||
- env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11
|
- env: GCC_VERSION=9 BUILD_TYPE=Release CPP=17
|
||||||
os: linux
|
os: linux
|
||||||
addons: *clang35
|
addons: *gcc9
|
||||||
|
|
||||||
|
# Test clang-3.5: C++11, Build=Release
|
||||||
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11
|
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11
|
||||||
os: linux
|
os: linux
|
||||||
addons: *clang35
|
addons: *clang35
|
||||||
|
|
||||||
# osx
|
# Text osx
|
||||||
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
|
- env: BUILD_TYPE=Release CPP=11 ASAN=Off TSAN=Off
|
||||||
os: osx
|
os: osx
|
||||||
|
|
||||||
# Test clang-10.0: C++11, Build=Debug/Release
|
# Test clang-10.0: C++11, Build=Release
|
||||||
- env: CLANG_VERSION=10 BUILD_TYPE=Debug CPP=11
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
addons: *clang10
|
|
||||||
|
|
||||||
- env: CLANG_VERSION=10 BUILD_TYPE=Release CPP=11 ASAN=On
|
- env: CLANG_VERSION=10 BUILD_TYPE=Release CPP=11 ASAN=On
|
||||||
os: linux
|
os: linux
|
||||||
dist: bionic
|
dist: bionic
|
||||||
addons: *clang10
|
addons: *clang10
|
||||||
|
|
||||||
|
# Test clang-10.0: C++17, Build=Debug
|
||||||
|
- env: CLANG_VERSION=10 BUILD_TYPE=Debug CPP=17 ASAN=Off
|
||||||
|
os: linux
|
||||||
|
dist: bionic
|
||||||
|
addons: *clang10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
if(${CMAKE_VERSION} VERSION_LESS 3.11)
|
|
||||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
|
||||||
else()
|
|
||||||
cmake_policy(VERSION 3.11)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
enable_language(C)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
# Start spdlog project
|
# Start spdlog project
|
||||||
@@ -23,6 +15,13 @@ message(STATUS "Build spdlog: ${SPDLOG_VERSION}")
|
|||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Set CMake policies to support later version behaviour
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
if(POLICY CMP0077)
|
||||||
|
cmake_policy(SET CMP0077 NEW) # option() honors variables already set
|
||||||
|
endif()
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
# Set default build to release
|
# Set default build to release
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
@@ -38,9 +37,14 @@ if(NOT CMAKE_CXX_STANDARD)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# make sure __cplusplus is defined when using msvc and enable parallel build
|
||||||
|
if(MSVC)
|
||||||
|
string(APPEND CMAKE_CXX_FLAGS " /Zc:__cplusplus /MP")
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN")
|
if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS")
|
||||||
set(CMAKE_CXX_EXTENSIONS ON)
|
set(CMAKE_CXX_EXTENSIONS ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -95,10 +99,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)
|
||||||
|
else()
|
||||||
|
set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE)
|
||||||
|
set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
option(SPDLOG_CLOCK_COARSE
|
option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
|
||||||
"Use the much faster (but much less accurate) CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
|
else()
|
||||||
|
set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF)
|
option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF)
|
||||||
@@ -108,6 +117,7 @@ option(
|
|||||||
SPDLOG_NO_ATOMIC_LEVELS
|
SPDLOG_NO_ATOMIC_LEVELS
|
||||||
"prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently"
|
"prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently"
|
||||||
OFF)
|
OFF)
|
||||||
|
option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF)
|
||||||
|
|
||||||
# clang-tidy
|
# clang-tidy
|
||||||
if(${CMAKE_VERSION} VERSION_GREATER "3.5")
|
if(${CMAKE_VERSION} VERSION_GREATER "3.5")
|
||||||
@@ -131,7 +141,7 @@ if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
|
|||||||
list(APPEND SPDLOG_SRCS src/fmt.cpp)
|
list(APPEND SPDLOG_SRCS src/fmt.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(SPDLOG_BUILD_SHARED)
|
if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
|
||||||
list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
|
list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
|
||||||
@@ -139,7 +149,8 @@ if(SPDLOG_BUILD_SHARED)
|
|||||||
add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
|
add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
|
||||||
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
|
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_compile_options(spdlog PUBLIC /wd4251 /wd4275)
|
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
|
||||||
|
/wd4275>)
|
||||||
endif()
|
endif()
|
||||||
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 PRIVATE FMT_EXPORT PUBLIC FMT_SHARED)
|
target_compile_definitions(spdlog PRIVATE FMT_EXPORT PUBLIC FMT_SHARED)
|
||||||
@@ -179,7 +190,7 @@ target_link_libraries(spdlog_header_only INTERFACE Threads::Threads)
|
|||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
|
if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
|
||||||
if(NOT TARGET fmt::fmt)
|
if(NOT TARGET fmt::fmt)
|
||||||
find_package(fmt 5.3.0 REQUIRED)
|
find_package(fmt CONFIG REQUIRED)
|
||||||
endif()
|
endif()
|
||||||
target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL)
|
target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL)
|
||||||
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL)
|
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL)
|
||||||
@@ -196,11 +207,20 @@ 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()
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
# Add required libraries for Android CMake build
|
||||||
|
# ---------------------------------------------------------------------------------------
|
||||||
|
if(ANDROID)
|
||||||
|
target_link_libraries(spdlog PUBLIC log)
|
||||||
|
target_link_libraries(spdlog_header_only INTERFACE log)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
# Misc definitions according to tweak options
|
# Misc definitions according to tweak options
|
||||||
# ---------------------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------------------
|
||||||
set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT})
|
set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT})
|
||||||
foreach(SPDLOG_OPTION
|
foreach(
|
||||||
|
SPDLOG_OPTION
|
||||||
SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
SPDLOG_WCHAR_FILENAMES
|
SPDLOG_WCHAR_FILENAMES
|
||||||
SPDLOG_NO_EXCEPTIONS
|
SPDLOG_NO_EXCEPTIONS
|
||||||
@@ -209,7 +229,7 @@ foreach(SPDLOG_OPTION
|
|||||||
SPDLOG_NO_THREAD_ID
|
SPDLOG_NO_THREAD_ID
|
||||||
SPDLOG_NO_TLS
|
SPDLOG_NO_TLS
|
||||||
SPDLOG_NO_ATOMIC_LEVELS
|
SPDLOG_NO_ATOMIC_LEVELS
|
||||||
)
|
SPDLOG_DISABLE_DEFAULT_LOGGER)
|
||||||
if(${SPDLOG_OPTION})
|
if(${SPDLOG_OPTION})
|
||||||
target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION})
|
target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION})
|
||||||
target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION})
|
target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION})
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -1,6 +1,6 @@
|
|||||||
# spdlog
|
# spdlog
|
||||||
|
|
||||||
Very fast, header-only/compiled, C++ logging library. [](https://travis-ci.org/gabime/spdlog) [](https://ci.appveyor.com/project/gabime/spdlog) [](https://github.com/gabime/spdlog/releases/latest)
|
Very fast, header-only/compiled, C++ logging library. [](https://travis-ci.com/gabime/spdlog) [](https://ci.appveyor.com/project/gabime/spdlog) [](https://github.com/gabime/spdlog/releases/latest)
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
#### Header only version
|
#### Header only version
|
||||||
@@ -25,12 +25,14 @@ $ cmake .. && make -j
|
|||||||
* Homebrew: `brew install spdlog`
|
* Homebrew: `brew install spdlog`
|
||||||
* MacPorts: `sudo port install spdlog`
|
* MacPorts: `sudo port install spdlog`
|
||||||
* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean`
|
* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean`
|
||||||
* Fedora: `yum install spdlog`
|
* Fedora: `dnf install spdlog`
|
||||||
* Gentoo: `emerge dev-libs/spdlog`
|
* Gentoo: `emerge dev-libs/spdlog`
|
||||||
* Arch Linux: `pacman -S spdlog`
|
* Arch Linux: `pacman -S spdlog`
|
||||||
* vcpkg: `vcpkg install spdlog`
|
* vcpkg: `vcpkg install spdlog`
|
||||||
* conan: `spdlog/[>=1.4.1]`
|
* conan: `spdlog/[>=1.4.1]`
|
||||||
* conda: `conda install -c conda-forge spdlog`
|
* conda: `conda install -c conda-forge spdlog`
|
||||||
|
* build2: ```depends: spdlog ^1.8.2```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -45,8 +47,9 @@ $ cmake .. && make -j
|
|||||||
* Daily log files.
|
* Daily log files.
|
||||||
* Console logging (colors supported).
|
* Console logging (colors supported).
|
||||||
* syslog.
|
* syslog.
|
||||||
* Windows debugger (```OutputDebugString(..)```)
|
* Windows event log.
|
||||||
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
|
* Windows debugger (```OutputDebugString(..)```).
|
||||||
|
* Easily [extendable](https://github.com/gabime/spdlog/wiki/4.-Sinks#implementing-your-own-sink) with custom log targets.
|
||||||
* Log filtering - log levels can be modified in runtime as well as in compile time.
|
* Log filtering - log levels can be modified in runtime as well as in compile time.
|
||||||
* Support for loading log levels from argv or from environment var.
|
* Support for loading log levels from argv or from environment var.
|
||||||
* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display later on demand.
|
* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display later on demand.
|
||||||
@@ -56,7 +59,6 @@ $ cmake .. && make -j
|
|||||||
#### Basic usage
|
#### Basic usage
|
||||||
```c++
|
```c++
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "spdlog/sinks/basic_file_sink.h"
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
@@ -79,10 +81,6 @@ int main()
|
|||||||
// define SPDLOG_ACTIVE_LEVEL to desired level
|
// define SPDLOG_ACTIVE_LEVEL to desired level
|
||||||
SPDLOG_TRACE("Some trace message with param {}", 42);
|
SPDLOG_TRACE("Some trace message with param {}", 42);
|
||||||
SPDLOG_DEBUG("Some debug message");
|
SPDLOG_DEBUG("Some debug message");
|
||||||
|
|
||||||
// Set the default logger to file logger
|
|
||||||
auto file_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic.txt");
|
|
||||||
spdlog::set_default_logger(file_logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -145,8 +143,9 @@ void daily_example()
|
|||||||
---
|
---
|
||||||
#### Backtrace support
|
#### Backtrace support
|
||||||
```c++
|
```c++
|
||||||
// Loggers can store in a ring buffer all messages (including debug/trace) and display later on demand.
|
// Debug messages can be stored in a ring buffer instead of being logged immediately.
|
||||||
// When needed, call dump_backtrace() to see them
|
// This is useful in order to display debug logs only when really nededed (e.g. when error happens).
|
||||||
|
// When needed, call dump_backtrace() to see them.
|
||||||
|
|
||||||
spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. Older messages will be dropped.
|
spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. Older messages will be dropped.
|
||||||
// or my_logger->enable_backtrace(32)..
|
// or my_logger->enable_backtrace(32)..
|
||||||
@@ -169,6 +168,20 @@ spdlog::flush_every(std::chrono::seconds(3));
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
#### Stopwatch
|
||||||
|
```c++
|
||||||
|
// Stopwatch support for spdlog
|
||||||
|
#include "spdlog/stopwatch.h"
|
||||||
|
void stopwatch_example()
|
||||||
|
{
|
||||||
|
spdlog::stopwatch sw;
|
||||||
|
spdlog::debug("Elapsed {}", sw);
|
||||||
|
spdlog::debug("Elapsed {:.3}", sw);
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
#### Log binary data in hex
|
#### Log binary data in hex
|
||||||
```c++
|
```c++
|
||||||
@@ -179,6 +192,7 @@ spdlog::flush_every(std::chrono::seconds(3));
|
|||||||
// {:s} - don't separate each byte with space.
|
// {:s} - don't separate each byte with space.
|
||||||
// {:p} - don't print the position on each line start.
|
// {:p} - don't print the position on each line start.
|
||||||
// {:n} - don't split the output to lines.
|
// {:n} - don't split the output to lines.
|
||||||
|
// {:a} - show ASCII if :n is not set.
|
||||||
|
|
||||||
#include "spdlog/fmt/bin_to_hex.h"
|
#include "spdlog/fmt/bin_to_hex.h"
|
||||||
|
|
||||||
|
|||||||
43
appveyor.yml
43
appveyor.yml
@@ -4,32 +4,59 @@ environment:
|
|||||||
matrix:
|
matrix:
|
||||||
- GENERATOR: '"Visual Studio 14 2015"'
|
- GENERATOR: '"Visual Studio 14 2015"'
|
||||||
BUILD_TYPE: Debug
|
BUILD_TYPE: Debug
|
||||||
WCHAR: 'OFF'
|
|
||||||
BUILD_SHARED: 'OFF'
|
BUILD_SHARED: 'OFF'
|
||||||
|
WCHAR: 'OFF'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 14 2015"'
|
- GENERATOR: '"Visual Studio 14 2015"'
|
||||||
BUILD_TYPE: Release
|
BUILD_TYPE: Release
|
||||||
WCHAR: 'ON'
|
|
||||||
BUILD_SHARED: 'OFF'
|
BUILD_SHARED: 'OFF'
|
||||||
|
WCHAR: 'ON'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
||||||
BUILD_TYPE: Debug
|
BUILD_TYPE: Debug
|
||||||
WCHAR: 'ON'
|
|
||||||
BUILD_SHARED: 'OFF'
|
BUILD_SHARED: 'OFF'
|
||||||
|
WCHAR: 'ON'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
- GENERATOR: '"Visual Studio 14 2015 Win64"'
|
||||||
BUILD_TYPE: Release
|
BUILD_TYPE: Release
|
||||||
WCHAR: 'ON'
|
|
||||||
BUILD_SHARED: 'OFF'
|
BUILD_SHARED: 'OFF'
|
||||||
|
WCHAR: 'ON'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
||||||
BUILD_TYPE: Debug
|
BUILD_TYPE: Debug
|
||||||
|
BUILD_SHARED: 'OFF'
|
||||||
WCHAR: 'ON'
|
WCHAR: 'ON'
|
||||||
BUILD_SHARED: 'OFF'
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
||||||
BUILD_TYPE: Release
|
BUILD_TYPE: Release
|
||||||
WCHAR: 'OFF'
|
|
||||||
BUILD_SHARED: 'OFF'
|
BUILD_SHARED: 'OFF'
|
||||||
|
WCHAR: 'OFF'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
||||||
BUILD_TYPE: Release
|
BUILD_TYPE: Release
|
||||||
WCHAR: 'OFF'
|
|
||||||
BUILD_SHARED: 'ON'
|
BUILD_SHARED: 'ON'
|
||||||
|
WCHAR: 'OFF'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'ON'
|
||||||
|
- GENERATOR: '"Visual Studio 15 2017 Win64"'
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
BUILD_SHARED: 'ON'
|
||||||
|
WCHAR: 'ON'
|
||||||
|
WCHAR_FILES: 'ON'
|
||||||
|
BUILD_EXAMPLE: 'OFF'
|
||||||
|
- GENERATOR: '"Visual Studio 16 2019" -A x64'
|
||||||
|
BUILD_TYPE: Release
|
||||||
|
BUILD_SHARED: 'ON'
|
||||||
|
WCHAR: 'OFF'
|
||||||
|
WCHAR_FILES: 'OFF'
|
||||||
|
BUILD_EXAMPLE: 'OFF'
|
||||||
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
build_script:
|
build_script:
|
||||||
- cmd: >-
|
- cmd: >-
|
||||||
set
|
set
|
||||||
@@ -40,7 +67,7 @@ build_script:
|
|||||||
|
|
||||||
set PATH=%PATH%;C:\Program Files\Git\usr\bin
|
set PATH=%PATH%;C:\Program Files\Git\usr\bin
|
||||||
|
|
||||||
cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSPDLOG_WCHAR_SUPPORT=%WCHAR% -DSPDLOG_BUILD_SHARED=%BUILD_SHARED% -DSPDLOG_BUILD_EXAMPLE=ON -DSPDLOG_BUILD_EXAMPLE_HO=ON -DSPDLOG_BUILD_TESTS=ON -DSPDLOG_BUILD_TESTS_HO=OFF -DSPDLOG_BUILD_WARNINGS=ON
|
cmake -G %GENERATOR% -D CMAKE_BUILD_TYPE=%BUILD_TYPE% -D BUILD_SHARED_LIBS=%BUILD_SHARED% -D SPDLOG_WCHAR_SUPPORT=%WCHAR% -D SPDLOG_WCHAR_FILENAMES=%WCHAR_FILES% -D SPDLOG_BUILD_EXAMPLE=%BUILD_EXAMPLE% -D SPDLOG_BUILD_EXAMPLE_HO=%BUILD_EXAMPLE% -D SPDLOG_BUILD_TESTS=ON -D SPDLOG_BUILD_TESTS_HO=OFF -D SPDLOG_BUILD_WARNINGS=ON ..
|
||||||
|
|
||||||
cmake --build . --config %BUILD_TYPE%
|
cmake --build . --config %BUILD_TYPE%
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.1)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(spdlog_bench CXX)
|
project(spdlog_bench CXX)
|
||||||
|
|
||||||
if(NOT TARGET spdlog)
|
if(NOT TARGET spdlog)
|
||||||
@@ -9,7 +9,22 @@ if(NOT TARGET spdlog)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
find_package(benchmark CONFIG REQUIRED)
|
find_package(benchmark CONFIG)
|
||||||
|
if(NOT benchmark_FOUND)
|
||||||
|
message(STATUS "Using CMake Version ${CMAKE_VERSION}")
|
||||||
|
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.11.0")
|
||||||
|
# User can fetch googlebenchmark
|
||||||
|
message(STATUS "Downloading GoogleBenchmark")
|
||||||
|
include(FetchContent)
|
||||||
|
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE INTERNAL "")
|
||||||
|
# Do not build and run googlebenchmark tests
|
||||||
|
FetchContent_Declare(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.5.2)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(googlebenchmark)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "GoogleBenchmark is missing. Use CMake >= 3.11 or download it")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(bench bench.cpp)
|
add_executable(bench bench.cpp)
|
||||||
spdlog_enable_warnings(bench)
|
spdlog_enable_warnings(bench)
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "spdlog/async.h"
|
#include "spdlog/async.h"
|
||||||
#include "spdlog/sinks/basic_file_sink.h"
|
#include "spdlog/sinks/basic_file_sink.h"
|
||||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
|
||||||
|
#ifdef SPDLOG_FMT_EXTERNAL
|
||||||
|
# include <fmt/format.h>
|
||||||
|
#else
|
||||||
|
# include "spdlog/fmt/bundled/format.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
@@ -48,14 +53,14 @@ int count_lines(const char *filename)
|
|||||||
|
|
||||||
void verify_file(const char *filename, int expected_count)
|
void verify_file(const char *filename, int expected_count)
|
||||||
{
|
{
|
||||||
spdlog::info("Verifying {} to contain {:n} line..", filename, expected_count);
|
spdlog::info("Verifying {} to contain {} line..", filename, expected_count);
|
||||||
auto count = count_lines(filename);
|
auto count = count_lines(filename);
|
||||||
if (count != expected_count)
|
if (count != expected_count)
|
||||||
{
|
{
|
||||||
spdlog::error("Test failed. {} has {:n} lines instead of {:n}", filename, count, expected_count);
|
spdlog::error("Test failed. {} has {} lines instead of {}", filename, count, expected_count);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
spdlog::info("Line count OK ({:n})\n", count);
|
spdlog::info("Line count OK ({})\n", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
@@ -98,11 +103,11 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
auto slot_size = sizeof(spdlog::details::async_msg);
|
auto slot_size = sizeof(spdlog::details::async_msg);
|
||||||
spdlog::info("-------------------------------------------------");
|
spdlog::info("-------------------------------------------------");
|
||||||
spdlog::info("Messages : {:n}", howmany);
|
spdlog::info("Messages : {:L}", howmany);
|
||||||
spdlog::info("Threads : {:n}", threads);
|
spdlog::info("Threads : {:L}", threads);
|
||||||
spdlog::info("Queue : {:n} slots", queue_size);
|
spdlog::info("Queue : {:L} slots", queue_size);
|
||||||
spdlog::info("Queue memory : {:n} x {} = {:n} KB ", queue_size, slot_size, (queue_size * slot_size) / 1024);
|
spdlog::info("Queue memory : {:L} x {:L} = {:L} KB ", queue_size, slot_size, (queue_size * slot_size) / 1024);
|
||||||
spdlog::info("Total iters : {:n}", iters);
|
spdlog::info("Total iters : {:L}", iters);
|
||||||
spdlog::info("-------------------------------------------------");
|
spdlog::info("-------------------------------------------------");
|
||||||
|
|
||||||
const char *filename = "logs/basic_async.log";
|
const char *filename = "logs/basic_async.log";
|
||||||
@@ -175,5 +180,5 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_co
|
|||||||
|
|
||||||
auto delta = high_resolution_clock::now() - start;
|
auto delta = high_resolution_clock::now() - start;
|
||||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||||
spdlog::info("Elapsed: {} secs\t {:n}/sec", delta_d, int(howmany / delta_d));
|
spdlog::info("Elapsed: {} secs\t {:L}/sec", delta_d, int(howmany / delta_d));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
#include "spdlog/sinks/null_sink.h"
|
#include "spdlog/sinks/null_sink.h"
|
||||||
#include "spdlog/sinks/rotating_file_sink.h"
|
#include "spdlog/sinks/rotating_file_sink.h"
|
||||||
|
|
||||||
|
#ifdef SPDLOG_FMT_EXTERNAL
|
||||||
|
# include <fmt/locale.h>
|
||||||
|
#else
|
||||||
|
# include "spdlog/fmt/bundled/format.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstdlib> // EXIT_FAILURE
|
#include <cstdlib> // EXIT_FAILURE
|
||||||
@@ -32,7 +38,7 @@ static const int max_threads = 1000;
|
|||||||
void bench_threaded_logging(size_t threads, int iters)
|
void bench_threaded_logging(size_t threads, int iters)
|
||||||
{
|
{
|
||||||
spdlog::info("**************************************************************");
|
spdlog::info("**************************************************************");
|
||||||
spdlog::info("Multi threaded: {:n} threads, {:n} messages", threads, iters);
|
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Multi threaded: {:L} threads, {:L} messages", threads, iters));
|
||||||
spdlog::info("**************************************************************");
|
spdlog::info("**************************************************************");
|
||||||
|
|
||||||
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
|
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
|
||||||
@@ -68,7 +74,7 @@ void bench_threaded_logging(size_t threads, int iters)
|
|||||||
void bench_single_threaded(int iters)
|
void bench_single_threaded(int iters)
|
||||||
{
|
{
|
||||||
spdlog::info("**************************************************************");
|
spdlog::info("**************************************************************");
|
||||||
spdlog::info("Single threaded: {:n} messages", iters);
|
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Single threaded: {} messages", iters));
|
||||||
spdlog::info("**************************************************************");
|
spdlog::info("**************************************************************");
|
||||||
|
|
||||||
auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
|
auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
|
||||||
@@ -122,7 +128,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
if (threads > max_threads)
|
if (threads > max_threads)
|
||||||
{
|
{
|
||||||
throw std::runtime_error(fmt::format("Number of threads exceeds maximum({}})", max_threads));
|
throw std::runtime_error(fmt::format("Number of threads exceeds maximum({})", max_threads));
|
||||||
}
|
}
|
||||||
|
|
||||||
bench_single_threaded(iters);
|
bench_single_threaded(iters);
|
||||||
@@ -152,7 +158,8 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log)
|
|||||||
auto delta = high_resolution_clock::now() - start;
|
auto delta = high_resolution_clock::now() - start;
|
||||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||||
|
|
||||||
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
spdlog::info(
|
||||||
|
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
|
||||||
spdlog::drop(log->name());
|
spdlog::drop(log->name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +189,8 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, size_t thread_co
|
|||||||
|
|
||||||
auto delta = high_resolution_clock::now() - start;
|
auto delta = high_resolution_clock::now() - start;
|
||||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||||
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
spdlog::info(
|
||||||
|
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
|
||||||
spdlog::drop(log->name());
|
spdlog::drop(log->name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +213,7 @@ void bench_default_api(int howmany, std::shared_ptr<spdlog::logger> log)
|
|||||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||||
spdlog::drop(log->name());
|
spdlog::drop(log->name());
|
||||||
spdlog::set_default_logger(std::move(orig_default));
|
spdlog::set_default_logger(std::move(orig_default));
|
||||||
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||||
}
|
}
|
||||||
|
|
||||||
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log)
|
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log)
|
||||||
@@ -232,7 +240,7 @@ void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log)
|
|||||||
auto delta_d = duration_cast<duration<double>>(delta).count();
|
auto delta_d = duration_cast<duration<double>>(delta).count();
|
||||||
spdlog::drop(log->name());
|
spdlog::drop(log->name());
|
||||||
spdlog::set_default_logger(std::move(orig_default));
|
spdlog::set_default_logger(std::move(orig_default));
|
||||||
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
|
spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16}/sec", log->name(), delta_d, int(howmany / delta_d));
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
@@ -34,14 +34,14 @@ void bench_formatters()
|
|||||||
for (auto &flag : all_flags)
|
for (auto &flag : all_flags)
|
||||||
{
|
{
|
||||||
auto pattern = std::string("%") + flag;
|
auto pattern = std::string("%") + flag;
|
||||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
|
||||||
|
|
||||||
// pattern = std::string("%16") + flag;
|
// pattern = std::string("%16") + flag;
|
||||||
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
// benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
|
||||||
//
|
//
|
||||||
// // bench center padding
|
// // bench center padding
|
||||||
// pattern = std::string("%=16") + flag;
|
// pattern = std::string("%=16") + flag;
|
||||||
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
// benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
// complex patterns
|
// complex patterns
|
||||||
@@ -52,7 +52,7 @@ void bench_formatters()
|
|||||||
};
|
};
|
||||||
for (auto &pattern : patterns)
|
for (auto &pattern : patterns)
|
||||||
{
|
{
|
||||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern)->Iterations(2500000);
|
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern)->Iterations(2500000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
|
benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern);
|
||||||
}
|
}
|
||||||
benchmark::Initialize(&argc, argv);
|
benchmark::Initialize(&argc, argv);
|
||||||
benchmark::RunSpecifiedBenchmarks();
|
benchmark::RunSpecifiedBenchmarks();
|
||||||
|
|||||||
@@ -18,23 +18,37 @@ set(CPACK_PACKAGE_RELOCATABLE ON CACHE BOOL "Build relocatable package")
|
|||||||
|
|
||||||
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
|
set(CPACK_RPM_PACKAGE_LICENSE "MIT")
|
||||||
set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries")
|
set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries")
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_SECTION "libs")
|
||||||
set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL})
|
set(CPACK_RPM_PACKAGE_URL ${CPACK_PROJECT_URL})
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE ${CPACK_PROJECT_URL})
|
||||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.")
|
set(CPACK_RPM_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.")
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.")
|
||||||
|
|
||||||
if(CPACK_PACKAGE_NAME)
|
if(CPACK_PACKAGE_NAME)
|
||||||
set(CPACK_RPM_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
|
set(CPACK_RPM_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||||
else()
|
else()
|
||||||
set(CPACK_RPM_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
|
set(CPACK_RPM_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME "${PROJECT_NAME}-${CPACK_PACKAGE_VERSION}")
|
||||||
|
set(CPACK_RPM_PACKAGE_NAME "${PROJECT_NAME}")
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_NAME "${PROJECT_NAME}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CPACK_RPM_PACKAGE_RELEASE)
|
if(CPACK_RPM_PACKAGE_RELEASE)
|
||||||
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}")
|
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}")
|
||||||
endif()
|
endif()
|
||||||
|
if(CPACK_DEBIAN_PACKAGE_RELEASE)
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}-${CPACK_DEBIAN_PACKAGE_RELEASE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CPACK_RPM_PACKAGE_ARCHITECTURE)
|
if(CPACK_RPM_PACKAGE_ARCHITECTURE)
|
||||||
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.${CPACK_RPM_PACKAGE_ARCHITECTURE}")
|
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.${CPACK_RPM_PACKAGE_ARCHITECTURE}")
|
||||||
endif()
|
endif()
|
||||||
|
if(CPACK_DEBIAN_PACKAGE_ARCHITECTURE)
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}")
|
||||||
|
endif()
|
||||||
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.rpm")
|
set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.rpm")
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.deb")
|
||||||
|
|
||||||
if(NOT CPACK_PACKAGE_RELOCATABLE)
|
if(NOT CPACK_PACKAGE_RELOCATABLE)
|
||||||
# Depend on pkgconfig rpm to create the system pkgconfig folder
|
# Depend on pkgconfig rpm to create the system pkgconfig folder
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function(spdlog_enable_warnings target_name)
|
|||||||
-Wextra
|
-Wextra
|
||||||
-Wconversion
|
-Wconversion
|
||||||
-pedantic
|
-pedantic
|
||||||
|
-Werror
|
||||||
-Wfatal-errors>
|
-Wfatal-errors>
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:${MSVC_OPTIONS}>)
|
$<$<CXX_COMPILER_ID:MSVC>:${MSVC_OPTIONS}>)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(spdlog_examples CXX)
|
project(spdlog_examples CXX)
|
||||||
|
|
||||||
if(NOT TARGET spdlog)
|
if(NOT TARGET spdlog)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ void rotating_example();
|
|||||||
void daily_example();
|
void daily_example();
|
||||||
void async_example();
|
void async_example();
|
||||||
void binary_example();
|
void binary_example();
|
||||||
|
void stopwatch_example();
|
||||||
void trace_example();
|
void trace_example();
|
||||||
void multi_sink_example();
|
void multi_sink_example();
|
||||||
void user_defined_example();
|
void user_defined_example();
|
||||||
@@ -21,7 +22,7 @@ void syslog_example();
|
|||||||
void custom_flags_example();
|
void custom_flags_example();
|
||||||
|
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include "spdlog/cfg/env.h" // for loading levels from the environment variable
|
#include "spdlog/cfg/env.h" // support for loading levels from the environment variable
|
||||||
|
|
||||||
int main(int, char *[])
|
int main(int, char *[])
|
||||||
{
|
{
|
||||||
@@ -71,6 +72,7 @@ int main(int, char *[])
|
|||||||
user_defined_example();
|
user_defined_example();
|
||||||
err_handler_example();
|
err_handler_example();
|
||||||
trace_example();
|
trace_example();
|
||||||
|
stopwatch_example();
|
||||||
custom_flags_example();
|
custom_flags_example();
|
||||||
|
|
||||||
// Flush all *registered* loggers using a worker thread every 3 seconds.
|
// Flush all *registered* loggers using a worker thread every 3 seconds.
|
||||||
@@ -127,7 +129,7 @@ void daily_example()
|
|||||||
#include "spdlog/cfg/env.h"
|
#include "spdlog/cfg/env.h"
|
||||||
void load_levels_example()
|
void load_levels_example()
|
||||||
{
|
{
|
||||||
// Set the log level to "info" and mylogger to 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 from command line:
|
// or from command line:
|
||||||
@@ -192,6 +194,16 @@ void trace_example()
|
|||||||
SPDLOG_LOGGER_TRACE(logger, "another trace message");
|
SPDLOG_LOGGER_TRACE(logger, "another trace message");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stopwatch example
|
||||||
|
#include "spdlog/stopwatch.h"
|
||||||
|
#include <thread>
|
||||||
|
void stopwatch_example()
|
||||||
|
{
|
||||||
|
spdlog::stopwatch sw;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(123));
|
||||||
|
spdlog::info("Stopwatch: {} seconds", sw);
|
||||||
|
}
|
||||||
|
|
||||||
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
|
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
|
||||||
void multi_sink_example()
|
void multi_sink_example()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ struct async_factory_impl
|
|||||||
auto tp = registry_inst.get_tp();
|
auto tp = registry_inst.get_tp();
|
||||||
if (tp == nullptr)
|
if (tp == nullptr)
|
||||||
{
|
{
|
||||||
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1);
|
tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1U);
|
||||||
registry_inst.set_tp(tp);
|
registry_inst.set_tp(tp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace spdlog {
|
|||||||
namespace cfg {
|
namespace cfg {
|
||||||
|
|
||||||
// search for SPDLOG_LEVEL= in the args and use it to init the levels
|
// search for SPDLOG_LEVEL= in the args and use it to init the levels
|
||||||
void load_argv_levels(int argc, const char **argv)
|
inline void load_argv_levels(int argc, const char **argv)
|
||||||
{
|
{
|
||||||
const std::string spdlog_level_prefix = "SPDLOG_LEVEL=";
|
const std::string spdlog_level_prefix = "SPDLOG_LEVEL=";
|
||||||
for (int i = 1; i < argc; i++)
|
for (int i = 1; i < argc; i++)
|
||||||
@@ -30,13 +30,12 @@ void load_argv_levels(int argc, const char **argv)
|
|||||||
if (arg.find(spdlog_level_prefix) == 0)
|
if (arg.find(spdlog_level_prefix) == 0)
|
||||||
{
|
{
|
||||||
auto levels_string = arg.substr(spdlog_level_prefix.size());
|
auto levels_string = arg.substr(spdlog_level_prefix.size());
|
||||||
auto levels = helpers::extract_levels(levels_string);
|
helpers::load_levels(levels_string);
|
||||||
details::registry::instance().update_levels(std::move(levels));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_argv_levels(int argc, char **argv)
|
inline void load_argv_levels(int argc, char **argv)
|
||||||
{
|
{
|
||||||
load_argv_levels(argc, const_cast<const char **>(argv));
|
load_argv_levels(argc, const_cast<const char **>(argv));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
// export SPDLOG_LEVEL=debug
|
// export SPDLOG_LEVEL=debug
|
||||||
//
|
//
|
||||||
// turn off all logging except for logger1:
|
// turn off all logging except for logger1:
|
||||||
// export SPDLOG_LEVEL="off,logger1=debug"
|
// export SPDLOG_LEVEL="*=off,logger1=debug"
|
||||||
//
|
//
|
||||||
|
|
||||||
// turn off all logging except for logger1 and logger2:
|
// turn off all logging except for logger1 and logger2:
|
||||||
@@ -25,11 +25,13 @@
|
|||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace cfg {
|
namespace cfg {
|
||||||
void load_env_levels()
|
inline void load_env_levels()
|
||||||
{
|
{
|
||||||
auto env_val = details::os::getenv("SPDLOG_LEVEL");
|
auto env_val = details::os::getenv("SPDLOG_LEVEL");
|
||||||
auto levels = helpers::extract_levels(env_val);
|
if (!env_val.empty())
|
||||||
details::registry::instance().update_levels(std::move(levels));
|
{
|
||||||
|
helpers::load_levels(env_val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cfg
|
} // namespace cfg
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <spdlog/details/os.h>
|
#include <spdlog/details/os.h>
|
||||||
#include <spdlog/details/registry.h>
|
#include <spdlog/details/registry.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -78,24 +79,40 @@ inline std::unordered_map<std::string, std::string> extract_key_vals_(const std:
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE log_levels extract_levels(const std::string &input)
|
SPDLOG_INLINE void load_levels(const std::string &input)
|
||||||
{
|
{
|
||||||
|
if (input.empty() || input.size() > 512)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto key_vals = extract_key_vals_(input);
|
auto key_vals = extract_key_vals_(input);
|
||||||
log_levels rv;
|
std::unordered_map<std::string, level::level_enum> levels;
|
||||||
|
level::level_enum global_level = level::info;
|
||||||
|
bool global_level_found = false;
|
||||||
|
|
||||||
for (auto &name_level : key_vals)
|
for (auto &name_level : key_vals)
|
||||||
{
|
{
|
||||||
auto &logger_name = name_level.first;
|
auto &logger_name = name_level.first;
|
||||||
auto level_name = to_lower_(name_level.second);
|
auto level_name = to_lower_(name_level.second);
|
||||||
auto level = level::from_str(level_name);
|
auto level = level::from_str(level_name);
|
||||||
// fallback to "info" if unrecognized level name
|
// ignore unrecognized level names
|
||||||
if (level == level::off && level_name != "off")
|
if (level == level::off && level_name != "off")
|
||||||
{
|
{
|
||||||
level = level::info;
|
continue;
|
||||||
}
|
}
|
||||||
rv.set(logger_name, level);
|
if (logger_name.empty()) // no logger name indicate global level
|
||||||
|
{
|
||||||
|
global_level_found = true;
|
||||||
|
global_level = level;
|
||||||
}
|
}
|
||||||
return rv;
|
else
|
||||||
|
{
|
||||||
|
levels[logger_name] = level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
details::registry::instance().set_levels(std::move(levels), global_level_found ? &global_level : nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace helpers
|
} // namespace helpers
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <spdlog/cfg/log_levels.h>
|
#include <spdlog/common.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace cfg {
|
namespace cfg {
|
||||||
@@ -17,7 +18,7 @@ namespace helpers {
|
|||||||
// turn off all logging except for logger1: "off,logger1=debug"
|
// turn off all logging except for logger1: "off,logger1=debug"
|
||||||
// turn off all logging except for logger1 and logger2: "off,logger1=debug,logger2=info"
|
// turn off all logging except for logger1 and logger2: "off,logger1=debug,logger2=info"
|
||||||
//
|
//
|
||||||
SPDLOG_API log_levels extract_levels(const std::string &txt);
|
SPDLOG_API void load_levels(const std::string &txt);
|
||||||
} // namespace helpers
|
} // namespace helpers
|
||||||
|
|
||||||
} // namespace cfg
|
} // namespace cfg
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
|
|
||||||
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <spdlog/common.h>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace spdlog {
|
|
||||||
namespace cfg {
|
|
||||||
class log_levels
|
|
||||||
{
|
|
||||||
std::unordered_map<std::string, spdlog::level::level_enum> levels_;
|
|
||||||
spdlog::level::level_enum default_level_ = level::info;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void set(const std::string &logger_name, level::level_enum lvl)
|
|
||||||
{
|
|
||||||
if (logger_name.empty())
|
|
||||||
{
|
|
||||||
default_level_ = lvl;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
levels_[logger_name] = lvl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_default(level::level_enum lvl)
|
|
||||||
{
|
|
||||||
default_level_ = lvl;
|
|
||||||
}
|
|
||||||
|
|
||||||
level::level_enum get(const std::string &logger_name)
|
|
||||||
{
|
|
||||||
auto it = levels_.find(logger_name);
|
|
||||||
return it != levels_.end() ? it->second : default_level_;
|
|
||||||
}
|
|
||||||
|
|
||||||
level::level_enum default_level()
|
|
||||||
{
|
|
||||||
return default_level_;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace cfg
|
|
||||||
} // namespace spdlog
|
|
||||||
@@ -7,13 +7,20 @@
|
|||||||
# include <spdlog/common.h>
|
# include <spdlog/common.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iterator>
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace level {
|
namespace level {
|
||||||
|
|
||||||
|
#if __cplusplus >= 201703L
|
||||||
|
constexpr
|
||||||
|
#endif
|
||||||
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
|
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
|
||||||
|
|
||||||
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES;
|
static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES;
|
||||||
|
|
||||||
SPDLOG_INLINE string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
|
SPDLOG_INLINE const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
|
||||||
{
|
{
|
||||||
return level_string_views[l];
|
return level_string_views[l];
|
||||||
}
|
}
|
||||||
@@ -25,15 +32,10 @@ SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOE
|
|||||||
|
|
||||||
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT
|
SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT
|
||||||
{
|
{
|
||||||
int level = 0;
|
auto it = std::find(std::begin(level_string_views), std::end(level_string_views), name);
|
||||||
for (const auto &level_str : level_string_views)
|
if (it != std::end(level_string_views))
|
||||||
{
|
return static_cast<level::level_enum>(std::distance(std::begin(level_string_views), it));
|
||||||
if (level_str == name)
|
|
||||||
{
|
|
||||||
return static_cast<level::level_enum>(level);
|
|
||||||
}
|
|
||||||
level++;
|
|
||||||
}
|
|
||||||
// check also for "warn" and "err" before giving up..
|
// check also for "warn" and "err" before giving up..
|
||||||
if (name == "warn")
|
if (name == "warn")
|
||||||
{
|
{
|
||||||
@@ -54,7 +56,7 @@ SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg)
|
|||||||
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno)
|
SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno)
|
||||||
{
|
{
|
||||||
memory_buf_t outbuf;
|
memory_buf_t outbuf;
|
||||||
fmt::format_system_error(outbuf, last_errno, msg);
|
fmt::format_system_error(outbuf, last_errno, msg.c_str());
|
||||||
msg_ = fmt::to_string(outbuf);
|
msg_ = fmt::to_string(outbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,16 @@
|
|||||||
|
|
||||||
#include <spdlog/fmt/fmt.h>
|
#include <spdlog/fmt/fmt.h>
|
||||||
|
|
||||||
|
// backward compatibility with fmt versions older than 8
|
||||||
|
#if FMT_VERSION >= 80000
|
||||||
|
# define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
|
||||||
|
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
|
||||||
|
# include <spdlog/fmt/xchar.h>
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# define SPDLOG_FMT_RUNTIME(format_string) format_string
|
||||||
|
#endif
|
||||||
|
|
||||||
// visual studio upto 2013 does not support noexcept nor constexpr
|
// visual studio upto 2013 does not support noexcept nor constexpr
|
||||||
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
#if defined(_MSC_VER) && (_MSC_VER < 1900)
|
||||||
# define SPDLOG_NOEXCEPT _NOEXCEPT
|
# define SPDLOG_NOEXCEPT _NOEXCEPT
|
||||||
@@ -71,11 +81,12 @@
|
|||||||
printf("spdlog fatal error: %s\n", ex.what()); \
|
printf("spdlog fatal error: %s\n", ex.what()); \
|
||||||
std::abort(); \
|
std::abort(); \
|
||||||
} while (0)
|
} while (0)
|
||||||
#define SPDLOG_CATCH_ALL()
|
# define SPDLOG_CATCH_STD
|
||||||
#else
|
#else
|
||||||
# define SPDLOG_TRY try
|
# define SPDLOG_TRY try
|
||||||
# define SPDLOG_THROW(ex) throw(ex)
|
# define SPDLOG_THROW(ex) throw(ex)
|
||||||
#define SPDLOG_CATCH_ALL() catch (...)
|
# define SPDLOG_CATCH_STD \
|
||||||
|
catch (const std::exception &) {}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
@@ -88,7 +99,9 @@ class sink;
|
|||||||
|
|
||||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
|
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
|
||||||
using filename_t = std::wstring;
|
using filename_t = std::wstring;
|
||||||
#define SPDLOG_FILENAME_T(s) L##s
|
// allow macro expansion to occur in SPDLOG_FILENAME_T
|
||||||
|
# define SPDLOG_FILENAME_T_INNER(s) L##s
|
||||||
|
# define SPDLOG_FILENAME_T(s) SPDLOG_FILENAME_T_INNER(s)
|
||||||
#else
|
#else
|
||||||
using filename_t = std::string;
|
using filename_t = std::string;
|
||||||
# define SPDLOG_FILENAME_T(s) s
|
# define SPDLOG_FILENAME_T(s) s
|
||||||
@@ -101,6 +114,7 @@ using err_handler = std::function<void(const std::string &err_msg)>;
|
|||||||
using string_view_t = fmt::basic_string_view<char>;
|
using string_view_t = fmt::basic_string_view<char>;
|
||||||
using wstring_view_t = fmt::basic_string_view<wchar_t>;
|
using wstring_view_t = fmt::basic_string_view<wchar_t>;
|
||||||
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;
|
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;
|
||||||
|
using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>;
|
||||||
|
|
||||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
# ifndef _WIN32
|
# ifndef _WIN32
|
||||||
@@ -148,10 +162,19 @@ enum level_enum
|
|||||||
n_levels
|
n_levels
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define SPDLOG_LEVEL_NAME_TRACE string_view_t("trace", 5)
|
||||||
|
#define SPDLOG_LEVEL_NAME_DEBUG string_view_t("debug", 5)
|
||||||
|
#define SPDLOG_LEVEL_NAME_INFO string_view_t("info", 4)
|
||||||
|
#define SPDLOG_LEVEL_NAME_WARNING string_view_t("warning", 7)
|
||||||
|
#define SPDLOG_LEVEL_NAME_ERROR string_view_t("error", 5)
|
||||||
|
#define SPDLOG_LEVEL_NAME_CRITICAL string_view_t("critical", 8)
|
||||||
|
#define SPDLOG_LEVEL_NAME_OFF string_view_t("off", 3)
|
||||||
|
|
||||||
#if !defined(SPDLOG_LEVEL_NAMES)
|
#if !defined(SPDLOG_LEVEL_NAMES)
|
||||||
# define SPDLOG_LEVEL_NAMES \
|
# define SPDLOG_LEVEL_NAMES \
|
||||||
{ \
|
{ \
|
||||||
"trace", "debug", "info", "warning", "error", "critical", "off" \
|
SPDLOG_LEVEL_NAME_TRACE, SPDLOG_LEVEL_NAME_DEBUG, SPDLOG_LEVEL_NAME_INFO, SPDLOG_LEVEL_NAME_WARNING, SPDLOG_LEVEL_NAME_ERROR, \
|
||||||
|
SPDLOG_LEVEL_NAME_CRITICAL, SPDLOG_LEVEL_NAME_OFF \
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -163,11 +186,10 @@ enum level_enum
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SPDLOG_API string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
|
SPDLOG_API const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
|
||||||
SPDLOG_API const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
|
SPDLOG_API const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT;
|
||||||
SPDLOG_API spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT;
|
SPDLOG_API spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT;
|
||||||
|
|
||||||
using level_hasher = std::hash<int>;
|
|
||||||
} // namespace level
|
} // namespace level
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -204,8 +226,8 @@ private:
|
|||||||
std::string msg_;
|
std::string msg_;
|
||||||
};
|
};
|
||||||
|
|
||||||
SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno);
|
[[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno);
|
||||||
SPDLOG_API void throw_spdlog_ex(std::string msg);
|
[[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg);
|
||||||
|
|
||||||
struct source_loc
|
struct source_loc
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,12 +29,27 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate)
|
|||||||
{
|
{
|
||||||
close();
|
close();
|
||||||
filename_ = fname;
|
filename_ = fname;
|
||||||
auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab");
|
|
||||||
|
auto *mode = SPDLOG_FILENAME_T("ab");
|
||||||
|
auto *trunc_mode = SPDLOG_FILENAME_T("wb");
|
||||||
|
|
||||||
for (int tries = 0; tries < open_tries_; ++tries)
|
for (int tries = 0; tries < open_tries_; ++tries)
|
||||||
{
|
{
|
||||||
// create containing folder if not exists already.
|
// create containing folder if not exists already.
|
||||||
os::create_dir(os::dir_name(fname));
|
os::create_dir(os::dir_name(fname));
|
||||||
|
if (truncate)
|
||||||
|
{
|
||||||
|
// Truncate by opening-and-closing a tmp file in "wb" mode, always
|
||||||
|
// opening the actual log-we-write-to in "ab" mode, since that
|
||||||
|
// interacts more politely with eternal processes that might
|
||||||
|
// rotate/truncate the file underneath us.
|
||||||
|
std::FILE *tmp;
|
||||||
|
if (os::fopen_s(&tmp, fname, trunc_mode))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::fclose(tmp);
|
||||||
|
}
|
||||||
if (!os::fopen_s(&fd_, fname, mode))
|
if (!os::fopen_s(&fd_, fname, mode))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -118,7 +133,7 @@ SPDLOG_INLINE std::tuple<filename_t, filename_t> file_helper::split_by_extension
|
|||||||
}
|
}
|
||||||
|
|
||||||
// treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
|
// treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile"
|
||||||
auto folder_index = fname.rfind(details::os::folder_sep);
|
auto folder_index = fname.find_last_of(details::os::folder_seps_filename);
|
||||||
if (folder_index != filename_t::npos && folder_index >= ext_index - 1)
|
if (folder_index != filename_t::npos && folder_index >= ext_index - 1)
|
||||||
{
|
{
|
||||||
return std::make_tuple(fname, filename_t());
|
return std::make_tuple(fname, filename_t());
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const int open_tries_ = 5;
|
const int open_tries_ = 5;
|
||||||
const int open_interval_ = 10;
|
const unsigned int open_interval_ = 10;
|
||||||
std::FILE *fd_{nullptr};
|
std::FILE *fd_{nullptr};
|
||||||
filename_t filename_;
|
filename_t filename_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <iterator>
|
||||||
#include <spdlog/fmt/fmt.h>
|
#include <spdlog/fmt/fmt.h>
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ inline void pad2(int n, memory_buf_t &dest)
|
|||||||
}
|
}
|
||||||
else // unlikely, but just in case, let fmt deal with it
|
else // unlikely, but just in case, let fmt deal with it
|
||||||
{
|
{
|
||||||
fmt::format_to(dest, "{:02}", n);
|
fmt::format_to(std::back_inserter(dest), "{:02}", n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ struct SPDLOG_API log_msg
|
|||||||
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg);
|
log_msg(source_loc loc, string_view_t logger_name, level::level_enum lvl, string_view_t msg);
|
||||||
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg);
|
log_msg(string_view_t logger_name, level::level_enum lvl, string_view_t msg);
|
||||||
log_msg(const log_msg &other) = default;
|
log_msg(const log_msg &other) = default;
|
||||||
|
log_msg &operator=(const log_msg &other) = default;
|
||||||
|
|
||||||
string_view_t logger_name;
|
string_view_t logger_name;
|
||||||
level::level_enum level{level::off};
|
level::level_enum level{level::off};
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ public:
|
|||||||
return q_.overrun_counter();
|
return q_.overrun_counter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t size()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(queue_mutex_);
|
||||||
|
return q_.size();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::mutex queue_mutex_;
|
std::mutex queue_mutex_;
|
||||||
std::condition_variable push_cv_;
|
std::condition_variable push_cv_;
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
// avoid warning about unreachable statement at the end of filesize()
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4702)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Return file size according to open FILE* object
|
// Return file size according to open FILE* object
|
||||||
SPDLOG_INLINE size_t filesize(FILE *f)
|
SPDLOG_INLINE size_t filesize(FILE *f)
|
||||||
{
|
{
|
||||||
@@ -208,7 +214,7 @@ SPDLOG_INLINE size_t filesize(FILE *f)
|
|||||||
}
|
}
|
||||||
#if defined(_WIN32) && !defined(__CYGWIN__)
|
#if defined(_WIN32) && !defined(__CYGWIN__)
|
||||||
int fd = ::_fileno(f);
|
int fd = ::_fileno(f);
|
||||||
#if _WIN64 // 64 bits
|
# if defined(_WIN64) // 64 bits
|
||||||
__int64 ret = ::_filelengthi64(fd);
|
__int64 ret = ::_filelengthi64(fd);
|
||||||
if (ret >= 0)
|
if (ret >= 0)
|
||||||
{
|
{
|
||||||
@@ -249,6 +255,10 @@ SPDLOG_INLINE size_t filesize(FILE *f)
|
|||||||
return 0; // will not be reached.
|
return 0; // will not be reached.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Return utc offset in minutes or throw spdlog_ex on failure
|
// Return utc offset in minutes or throw spdlog_ex on failure
|
||||||
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm)
|
SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm)
|
||||||
{
|
{
|
||||||
@@ -356,7 +366,7 @@ SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT
|
|||||||
|
|
||||||
// This is avoid msvc issue in sleep_for that happens if the clock changes.
|
// This is avoid msvc issue in sleep_for that happens if the clock changes.
|
||||||
// See https://github.com/gabime/spdlog/issues/609
|
// See https://github.com/gabime/spdlog/issues/609
|
||||||
SPDLOG_INLINE void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT
|
SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
::Sleep(milliseconds);
|
::Sleep(milliseconds);
|
||||||
@@ -397,17 +407,26 @@ SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
static constexpr std::array<const char *, 14> terms = {
|
|
||||||
{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}};
|
|
||||||
|
|
||||||
const char *env_p = std::getenv("TERM");
|
static const bool result = []() {
|
||||||
if (env_p == nullptr)
|
const char *env_colorterm_p = std::getenv("COLORTERM");
|
||||||
|
if (env_colorterm_p != nullptr)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::array<const char *, 16> terms = {{"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux",
|
||||||
|
"msys", "putty", "rxvt", "screen", "vt100", "xterm", "alacritty", "vt102"}};
|
||||||
|
|
||||||
|
const char *env_term_p = std::getenv("TERM");
|
||||||
|
if (env_term_p == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const bool result =
|
return std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_term_p, term) != nullptr; });
|
||||||
std::any_of(terms.begin(), terms.end(), [&](const char *term) { return std::strstr(env_p, term) != nullptr; });
|
}();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -427,7 +446,7 @@ SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT
|
|||||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
||||||
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
|
SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
|
||||||
{
|
{
|
||||||
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()))
|
if (wstr.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) / 2 - 1)
|
||||||
{
|
{
|
||||||
throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8");
|
throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8");
|
||||||
}
|
}
|
||||||
@@ -459,6 +478,41 @@ SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
|
|||||||
|
|
||||||
throw_spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
|
throw_spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target)
|
||||||
|
{
|
||||||
|
if (str.size() > static_cast<size_t>((std::numeric_limits<int>::max)()) - 1)
|
||||||
|
{
|
||||||
|
throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16");
|
||||||
|
}
|
||||||
|
|
||||||
|
int str_size = static_cast<int>(str.size());
|
||||||
|
if (str_size == 0)
|
||||||
|
{
|
||||||
|
target.resize(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result_size = static_cast<int>(target.capacity());
|
||||||
|
if (str_size + 1 > result_size)
|
||||||
|
{
|
||||||
|
result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_size > 0)
|
||||||
|
{
|
||||||
|
target.resize(result_size);
|
||||||
|
result_size = ::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, str.data(), str_size, target.data(), result_size);
|
||||||
|
|
||||||
|
if (result_size > 0)
|
||||||
|
{
|
||||||
|
target.resize(result_size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_spdlog_ex(fmt::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError()));
|
||||||
|
}
|
||||||
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
||||||
|
|
||||||
// return true on success
|
// return true on success
|
||||||
@@ -489,15 +543,10 @@ SPDLOG_INLINE bool create_dir(filename_t path)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// support forward slash in windows
|
|
||||||
std::replace(path.begin(), path.end(), '/', folder_sep);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
size_t search_offset = 0;
|
size_t search_offset = 0;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
auto token_pos = path.find(folder_sep, search_offset);
|
auto token_pos = path.find_first_of(folder_seps_filename, search_offset);
|
||||||
// treat the entire path as a folder if no folder separator not found
|
// treat the entire path as a folder if no folder separator not found
|
||||||
if (token_pos == filename_t::npos)
|
if (token_pos == filename_t::npos)
|
||||||
{
|
{
|
||||||
@@ -523,11 +572,7 @@ SPDLOG_INLINE bool create_dir(filename_t path)
|
|||||||
// "abc///" => "abc//"
|
// "abc///" => "abc//"
|
||||||
SPDLOG_INLINE filename_t dir_name(filename_t path)
|
SPDLOG_INLINE filename_t dir_name(filename_t path)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
auto pos = path.find_last_of(folder_seps_filename);
|
||||||
// support forward slash in windows
|
|
||||||
std::replace(path.begin(), path.end(), '/', folder_sep);
|
|
||||||
#endif
|
|
||||||
auto pos = path.find_last_of(folder_sep);
|
|
||||||
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
|
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,11 +32,16 @@ SPDLOG_API std::tm gmtime() SPDLOG_NOEXCEPT;
|
|||||||
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL;
|
SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL;
|
||||||
|
|
||||||
// folder separator
|
// folder separator
|
||||||
|
#if !defined(SPDLOG_FOLDER_SEPS)
|
||||||
# ifdef _WIN32
|
# ifdef _WIN32
|
||||||
static const char folder_sep = '\\';
|
# define SPDLOG_FOLDER_SEPS "\\/"
|
||||||
# else
|
# else
|
||||||
SPDLOG_CONSTEXPR static const char folder_sep = '/';
|
# define SPDLOG_FOLDER_SEPS "/"
|
||||||
# endif
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SPDLOG_CONSTEXPR static const char folder_seps[] = SPDLOG_FOLDER_SEPS;
|
||||||
|
SPDLOG_CONSTEXPR static const filename_t::value_type folder_seps_filename[] = SPDLOG_FILENAME_T(SPDLOG_FOLDER_SEPS);
|
||||||
|
|
||||||
// fopen_s on non windows for writing
|
// fopen_s on non windows for writing
|
||||||
SPDLOG_API bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode);
|
SPDLOG_API bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode);
|
||||||
@@ -69,7 +74,7 @@ SPDLOG_API size_t thread_id() SPDLOG_NOEXCEPT;
|
|||||||
|
|
||||||
// This is avoid msvc issue in sleep_for that happens if the clock changes.
|
// This is avoid msvc issue in sleep_for that happens if the clock changes.
|
||||||
// See https://github.com/gabime/spdlog/issues/609
|
// See https://github.com/gabime/spdlog/issues/609
|
||||||
SPDLOG_API void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT;
|
SPDLOG_API void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT;
|
||||||
|
|
||||||
SPDLOG_API std::string filename_to_str(const filename_t &filename);
|
SPDLOG_API std::string filename_to_str(const filename_t &filename);
|
||||||
|
|
||||||
@@ -85,6 +90,8 @@ SPDLOG_API bool in_terminal(FILE *file) SPDLOG_NOEXCEPT;
|
|||||||
|
|
||||||
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
|
||||||
SPDLOG_API void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target);
|
SPDLOG_API void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target);
|
||||||
|
|
||||||
|
SPDLOG_API void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Return directory name from given path or empty string
|
// Return directory name from given path or empty string
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logge
|
|||||||
new_logger->set_error_handler(err_handler_);
|
new_logger->set_error_handler(err_handler_);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_logger->set_level(levels_.get(new_logger->name()));
|
// set new level according to previously configured level or default level
|
||||||
|
auto it = log_levels_.find(new_logger->name());
|
||||||
|
auto new_level = it != log_levels_.end() ? it->second : global_log_level_;
|
||||||
|
new_logger->set_level(new_level);
|
||||||
|
|
||||||
new_logger->flush_on(flush_level_);
|
new_logger->flush_on(flush_level_);
|
||||||
|
|
||||||
if (backtrace_n_messages_ > 0)
|
if (backtrace_n_messages_ > 0)
|
||||||
@@ -171,7 +175,7 @@ SPDLOG_INLINE void registry::set_level(level::level_enum log_level)
|
|||||||
{
|
{
|
||||||
l.second->set_level(log_level);
|
l.second->set_level(log_level);
|
||||||
}
|
}
|
||||||
levels_.set_default(log_level);
|
global_log_level_ = log_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level)
|
SPDLOG_INLINE void registry::flush_on(level::level_enum log_level)
|
||||||
@@ -191,14 +195,14 @@ SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval)
|
|||||||
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
|
periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE void registry::set_error_handler(void (*handler)(const std::string &msg))
|
SPDLOG_INLINE void registry::set_error_handler(err_handler handler)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(logger_map_mutex_);
|
std::lock_guard<std::mutex> lock(logger_map_mutex_);
|
||||||
for (auto &l : loggers_)
|
for (auto &l : loggers_)
|
||||||
{
|
{
|
||||||
l.second->set_error_handler(handler);
|
l.second->set_error_handler(handler);
|
||||||
}
|
}
|
||||||
err_handler_ = handler;
|
err_handler_ = std::move(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun)
|
SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun)
|
||||||
@@ -263,14 +267,24 @@ SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registrat
|
|||||||
automatic_registration_ = automatic_registration;
|
automatic_registration_ = automatic_registration;
|
||||||
}
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE void registry::update_levels(cfg::log_levels levels)
|
SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum *global_level)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(logger_map_mutex_);
|
std::lock_guard<std::mutex> lock(logger_map_mutex_);
|
||||||
levels_ = std::move(levels);
|
log_levels_ = std::move(levels);
|
||||||
for (auto &l : loggers_)
|
auto global_level_requested = global_level != nullptr;
|
||||||
|
global_log_level_ = global_level_requested ? *global_level : global_log_level_;
|
||||||
|
|
||||||
|
for (auto &logger : loggers_)
|
||||||
{
|
{
|
||||||
auto &logger = l.second;
|
auto logger_entry = log_levels_.find(logger.first);
|
||||||
logger->set_level(levels_.get(logger->name()));
|
if (logger_entry != log_levels_.end())
|
||||||
|
{
|
||||||
|
logger.second->set_level(logger_entry->second);
|
||||||
|
}
|
||||||
|
else if (global_level_requested)
|
||||||
|
{
|
||||||
|
logger.second->set_level(*global_level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
// This class is thread safe
|
// This class is thread safe
|
||||||
|
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
#include <spdlog/cfg/log_levels.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -28,6 +27,7 @@ class periodic_worker;
|
|||||||
class SPDLOG_API registry
|
class SPDLOG_API registry
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
using log_levels = std::unordered_map<std::string, level::level_enum>;
|
||||||
registry(const registry &) = delete;
|
registry(const registry &) = delete;
|
||||||
registry &operator=(const registry &) = delete;
|
registry &operator=(const registry &) = delete;
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
|
|
||||||
void flush_every(std::chrono::seconds interval);
|
void flush_every(std::chrono::seconds interval);
|
||||||
|
|
||||||
void set_error_handler(void (*handler)(const std::string &msg));
|
void set_error_handler(err_handler handler);
|
||||||
|
|
||||||
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun);
|
void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun);
|
||||||
|
|
||||||
@@ -80,7 +80,8 @@ public:
|
|||||||
|
|
||||||
void set_automatic_registration(bool automatic_registration);
|
void set_automatic_registration(bool automatic_registration);
|
||||||
|
|
||||||
void update_levels(cfg::log_levels levels);
|
// set levels for all existing/future loggers. global_level can be null if should not set.
|
||||||
|
void set_levels(log_levels levels, level::level_enum *global_level);
|
||||||
|
|
||||||
static registry &instance();
|
static registry &instance();
|
||||||
|
|
||||||
@@ -90,13 +91,15 @@ 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);
|
||||||
|
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_;
|
||||||
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
|
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
|
||||||
cfg::log_levels levels_;
|
log_levels log_levels_;
|
||||||
std::unique_ptr<formatter> formatter_;
|
std::unique_ptr<formatter> formatter_;
|
||||||
|
spdlog::level::level_enum global_log_level_ = level::info;
|
||||||
level::level_enum flush_level_ = level::off;
|
level::level_enum flush_level_ = level::off;
|
||||||
void (*err_handler_)(const std::string &msg);
|
err_handler err_handler_;
|
||||||
std::shared_ptr<thread_pool> tp_;
|
std::shared_ptr<thread_pool> tp_;
|
||||||
std::unique_ptr<periodic_worker> periodic_flusher_;
|
std::unique_ptr<periodic_worker> periodic_flusher_;
|
||||||
std::shared_ptr<logger> default_logger_;
|
std::shared_ptr<logger> default_logger_;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
#include <spdlog/details/os.h>
|
#include <spdlog/details/os.h>
|
||||||
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
#include <windows.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -144,7 +144,7 @@ public:
|
|||||||
|
|
||||||
// set TCP_NODELAY
|
// set TCP_NODELAY
|
||||||
int enable_flag = 1;
|
int enable_flag = 1;
|
||||||
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag));
|
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), sizeof(enable_flag));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send exactly n_bytes of the given data.
|
// Send exactly n_bytes of the given data.
|
||||||
|
|||||||
@@ -75,7 +75,11 @@ public:
|
|||||||
int last_errno = 0;
|
int last_errno = 0;
|
||||||
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next)
|
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next)
|
||||||
{
|
{
|
||||||
int const flags = SOCK_CLOEXEC;
|
#if defined(SOCK_CLOEXEC)
|
||||||
|
const int flags = SOCK_CLOEXEC;
|
||||||
|
#else
|
||||||
|
const int flags = 0;
|
||||||
|
#endif
|
||||||
socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol);
|
socket_ = ::socket(rp->ai_family, rp->ai_socktype | flags, rp->ai_protocol);
|
||||||
if (socket_ == -1)
|
if (socket_ == -1)
|
||||||
{
|
{
|
||||||
@@ -87,13 +91,10 @@ public:
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
last_errno = errno;
|
last_errno = errno;
|
||||||
::close(socket_);
|
::close(socket_);
|
||||||
socket_ = -1;
|
socket_ = -1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
::freeaddrinfo(addrinfo_result);
|
::freeaddrinfo(addrinfo_result);
|
||||||
if (socket_ == -1)
|
if (socket_ == -1)
|
||||||
{
|
{
|
||||||
@@ -102,11 +103,11 @@ public:
|
|||||||
|
|
||||||
// set TCP_NODELAY
|
// set TCP_NODELAY
|
||||||
int enable_flag = 1;
|
int enable_flag = 1;
|
||||||
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (char *)&enable_flag, sizeof(enable_flag));
|
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), sizeof(enable_flag));
|
||||||
|
|
||||||
// prevent sigpipe on systems where MSG_NOSIGNAL is not available
|
// prevent sigpipe on systems where MSG_NOSIGNAL is not available
|
||||||
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
||||||
::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, (char *)&enable_flag, sizeof(enable_flag));
|
::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char *>(&enable_flag), sizeof(enable_flag));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ SPDLOG_INLINE thread_pool::~thread_pool()
|
|||||||
t.join();
|
t.join();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SPDLOG_CATCH_ALL() {}
|
SPDLOG_CATCH_STD
|
||||||
}
|
}
|
||||||
|
|
||||||
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy)
|
void SPDLOG_INLINE thread_pool::post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy)
|
||||||
@@ -68,6 +68,11 @@ size_t SPDLOG_INLINE thread_pool::overrun_counter()
|
|||||||
return q_.overrun_counter();
|
return q_.overrun_counter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t SPDLOG_INLINE thread_pool::queue_size()
|
||||||
|
{
|
||||||
|
return q_.size();
|
||||||
|
}
|
||||||
|
|
||||||
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy)
|
void SPDLOG_INLINE thread_pool::post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy)
|
||||||
{
|
{
|
||||||
if (overflow_policy == async_overflow_policy::block)
|
if (overflow_policy == async_overflow_policy::block)
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ public:
|
|||||||
void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy);
|
void post_log(async_logger_ptr &&worker_ptr, const details::log_msg &msg, async_overflow_policy overflow_policy);
|
||||||
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy);
|
void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy);
|
||||||
size_t overrun_counter();
|
size_t overrun_counter();
|
||||||
|
size_t queue_size();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
q_type q_;
|
q_type q_;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
//
|
//
|
||||||
// Support for logging binary data as hex
|
// Support for logging binary data as hex
|
||||||
// format flags:
|
// format flags, any combination of the followng:
|
||||||
// {:X} - print in uppercase.
|
// {:X} - print in uppercase.
|
||||||
// {:s} - don't separate each byte with space.
|
// {:s} - don't separate each byte with space.
|
||||||
// {:p} - don't print the position on each line start.
|
// {:p} - don't print the position on each line start.
|
||||||
@@ -209,7 +209,7 @@ struct formatter<spdlog::details::dump_info<T>>
|
|||||||
|
|
||||||
if (put_positions)
|
if (put_positions)
|
||||||
{
|
{
|
||||||
fmt::format_to(inserter, "{:<04X}: ", pos);
|
fmt::format_to(inserter, "{:04X}: ", pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
--- Optional exception to the license ---
|
|
||||||
|
|
||||||
As an exception, if, as a result of your compiling your source code, portions
|
|
||||||
of this Software are embedded into a machine-executable object form of such
|
|
||||||
source code, you may redistribute such embedded portions in such object form
|
|
||||||
without including the above copyright and permission notices.
|
|
||||||
232
include/spdlog/fmt/bundled/args.h
Normal file
232
include/spdlog/fmt/bundled/args.h
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
// Formatting library for C++ - dynamic format arguments
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_ARGS_H_
|
||||||
|
#define FMT_ARGS_H_
|
||||||
|
|
||||||
|
#include <functional> // std::reference_wrapper
|
||||||
|
#include <memory> // std::unique_ptr
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||||
|
template <typename T>
|
||||||
|
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||||
|
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||||
|
return static_cast<const T&>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
class dynamic_arg_list {
|
||||||
|
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||||
|
// templates it doesn't complain about inability to deduce single translation
|
||||||
|
// unit for placing vtable. So storage_node_base is made a fake template.
|
||||||
|
template <typename = void> struct node {
|
||||||
|
virtual ~node() = default;
|
||||||
|
std::unique_ptr<node<>> next;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> struct typed_node : node<> {
|
||||||
|
T value;
|
||||||
|
|
||||||
|
template <typename Arg>
|
||||||
|
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
|
||||||
|
: value(arg.data(), arg.size()) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<node<>> head_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||||
|
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||||
|
auto& value = new_node->value;
|
||||||
|
new_node->next = std::move(head_);
|
||||||
|
head_ = std::move(new_node);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
A dynamic version of `fmt::format_arg_store`.
|
||||||
|
It's equipped with a storage to potentially temporary objects which lifetimes
|
||||||
|
could be shorter than the format arguments object.
|
||||||
|
|
||||||
|
It can be implicitly converted into `~fmt::basic_format_args` for passing
|
||||||
|
into type-erased formatting functions such as `~fmt::vformat`.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename Context>
|
||||||
|
class dynamic_format_arg_store
|
||||||
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||||
|
// Workaround a GCC template argument substitution bug.
|
||||||
|
: public basic_format_args<Context>
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
using char_type = typename Context::char_type;
|
||||||
|
|
||||||
|
template <typename T> struct need_copy {
|
||||||
|
static constexpr detail::type mapped_type =
|
||||||
|
detail::mapped_type_constant<T, Context>::value;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
value = !(detail::is_reference_wrapper<T>::value ||
|
||||||
|
std::is_same<T, basic_string_view<char_type>>::value ||
|
||||||
|
std::is_same<T, detail::std_string_view<char_type>>::value ||
|
||||||
|
(mapped_type != detail::type::cstring_type &&
|
||||||
|
mapped_type != detail::type::string_type &&
|
||||||
|
mapped_type != detail::type::custom_type))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using stored_type = conditional_t<detail::is_string<T>::value &&
|
||||||
|
!has_formatter<T, Context>::value &&
|
||||||
|
!detail::is_reference_wrapper<T>::value,
|
||||||
|
std::basic_string<char_type>, T>;
|
||||||
|
|
||||||
|
// Storage of basic_format_arg must be contiguous.
|
||||||
|
std::vector<basic_format_arg<Context>> data_;
|
||||||
|
std::vector<detail::named_arg_info<char_type>> named_info_;
|
||||||
|
|
||||||
|
// Storage of arguments not fitting into basic_format_arg must grow
|
||||||
|
// without relocation because items in data_ refer to it.
|
||||||
|
detail::dynamic_arg_list dynamic_args_;
|
||||||
|
|
||||||
|
friend class basic_format_args<Context>;
|
||||||
|
|
||||||
|
unsigned long long get_types() const {
|
||||||
|
return detail::is_unpacked_bit | data_.size() |
|
||||||
|
(named_info_.empty()
|
||||||
|
? 0ULL
|
||||||
|
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||||
|
}
|
||||||
|
|
||||||
|
const basic_format_arg<Context>* data() const {
|
||||||
|
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void emplace_arg(const T& arg) {
|
||||||
|
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||||
|
if (named_info_.empty()) {
|
||||||
|
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||||
|
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||||
|
}
|
||||||
|
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||||
|
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||||
|
data->pop_back();
|
||||||
|
};
|
||||||
|
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||||
|
guard{&data_, pop_one};
|
||||||
|
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||||
|
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||||
|
guard.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Adds an argument into the dynamic store for later passing to a formatting
|
||||||
|
function.
|
||||||
|
|
||||||
|
Note that custom types and string types (but not string views) are copied
|
||||||
|
into the store dynamically allocating memory if necessary.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
|
store.push_back(42);
|
||||||
|
store.push_back("abc");
|
||||||
|
store.push_back(1.5f);
|
||||||
|
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename T> void push_back(const T& arg) {
|
||||||
|
if (detail::const_check(need_copy<T>::value))
|
||||||
|
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||||
|
else
|
||||||
|
emplace_arg(detail::unwrap(arg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Adds a reference to the argument into the dynamic store for later passing to
|
||||||
|
a formatting function.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||||
|
char band[] = "Rolling Stones";
|
||||||
|
store.push_back(std::cref(band));
|
||||||
|
band[9] = 'c'; // Changing str affects the output.
|
||||||
|
std::string result = fmt::vformat("{}", store);
|
||||||
|
// result == "Rolling Scones"
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||||
|
static_assert(
|
||||||
|
need_copy<T>::value,
|
||||||
|
"objects of built-in types and string views are always copied");
|
||||||
|
emplace_arg(arg.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Adds named argument into the dynamic store for later passing to a formatting
|
||||||
|
function. ``std::reference_wrapper`` is supported to avoid copying of the
|
||||||
|
argument. The name is always copied into the store.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||||
|
const char_type* arg_name =
|
||||||
|
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||||
|
if (detail::const_check(need_copy<T>::value)) {
|
||||||
|
emplace_arg(
|
||||||
|
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||||
|
} else {
|
||||||
|
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Erase all elements from the store */
|
||||||
|
void clear() {
|
||||||
|
data_.clear();
|
||||||
|
named_info_.clear();
|
||||||
|
dynamic_args_ = detail::dynamic_arg_list();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Reserves space to store at least *new_cap* arguments including
|
||||||
|
*new_cap_named* named arguments.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||||
|
FMT_ASSERT(new_cap >= new_cap_named,
|
||||||
|
"Set of arguments includes set of named arguments");
|
||||||
|
data_.reserve(new_cap);
|
||||||
|
named_info_.reserve(new_cap_named);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_ARGS_H_
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
#ifndef FMT_CHRONO_H_
|
#ifndef FMT_CHRONO_H_
|
||||||
#define FMT_CHRONO_H_
|
#define FMT_CHRONO_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
#include "locale.h"
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
|||||||
// From fits in To without any problem.
|
// From fits in To without any problem.
|
||||||
} else {
|
} else {
|
||||||
// From does not always fit in To, resort to a dynamic check.
|
// From does not always fit in To, resort to a dynamic check.
|
||||||
if (from < T::min() || from > T::max()) {
|
if (from < (T::min)() || from > (T::max)()) {
|
||||||
// outside range.
|
// outside range.
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
@@ -72,43 +72,27 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
|
|||||||
static_assert(F::is_integer, "From must be integral");
|
static_assert(F::is_integer, "From must be integral");
|
||||||
static_assert(T::is_integer, "To must be integral");
|
static_assert(T::is_integer, "To must be integral");
|
||||||
|
|
||||||
if (F::is_signed && !T::is_signed) {
|
if (detail::const_check(F::is_signed && !T::is_signed)) {
|
||||||
// From may be negative, not allowed!
|
// From may be negative, not allowed!
|
||||||
if (fmt::internal::is_negative(from)) {
|
if (fmt::detail::is_negative(from)) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// From is positive. Can it always fit in To?
|
// From is positive. Can it always fit in To?
|
||||||
if (F::digits <= T::digits) {
|
if (F::digits > T::digits &&
|
||||||
// yes, From always fits in To.
|
from > static_cast<From>(detail::max_value<To>())) {
|
||||||
} else {
|
|
||||||
// from may not fit in To, we have to do a dynamic check
|
|
||||||
if (from > static_cast<From>(T::max())) {
|
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!F::is_signed && T::is_signed) {
|
if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
|
||||||
// can from be held in To?
|
from > static_cast<From>(detail::max_value<To>())) {
|
||||||
if (F::digits < T::digits) {
|
|
||||||
// yes, From always fits in To.
|
|
||||||
} else {
|
|
||||||
// from may not fit in To, we have to do a dynamic check
|
|
||||||
if (from > static_cast<From>(T::max())) {
|
|
||||||
// outside range.
|
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
return static_cast<To>(from); // Lossless conversion.
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// reaching here means all is ok for lossless conversion.
|
|
||||||
return static_cast<To>(from);
|
|
||||||
|
|
||||||
} // function
|
|
||||||
|
|
||||||
template <typename To, typename From,
|
template <typename To, typename From,
|
||||||
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
FMT_ENABLE_IF(std::is_same<From, To>::value)>
|
||||||
@@ -141,7 +125,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
|
|||||||
|
|
||||||
// catch the only happy case
|
// catch the only happy case
|
||||||
if (std::isfinite(from)) {
|
if (std::isfinite(from)) {
|
||||||
if (from >= T::lowest() && from <= T::max()) {
|
if (from >= T::lowest() && from <= (T::max)()) {
|
||||||
return static_cast<To>(from);
|
return static_cast<To>(from);
|
||||||
}
|
}
|
||||||
// not within range.
|
// not within range.
|
||||||
@@ -190,17 +174,16 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|||||||
// safe conversion to IntermediateRep
|
// safe conversion to IntermediateRep
|
||||||
IntermediateRep count =
|
IntermediateRep count =
|
||||||
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
lossless_integral_conversion<IntermediateRep>(from.count(), ec);
|
||||||
if (ec) {
|
if (ec) return {};
|
||||||
return {};
|
|
||||||
}
|
|
||||||
// multiply with Factor::num without overflow or underflow
|
// multiply with Factor::num without overflow or underflow
|
||||||
if (Factor::num != 1) {
|
if (detail::const_check(Factor::num != 1)) {
|
||||||
const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
|
const auto max1 = detail::max_value<IntermediateRep>() / Factor::num;
|
||||||
if (count > max1) {
|
if (count > max1) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
|
const auto min1 =
|
||||||
|
(std::numeric_limits<IntermediateRep>::min)() / Factor::num;
|
||||||
if (count < min1) {
|
if (count < min1) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
return {};
|
return {};
|
||||||
@@ -208,17 +191,9 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|||||||
count *= Factor::num;
|
count *= Factor::num;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this can't go wrong, right? den>0 is checked earlier.
|
if (detail::const_check(Factor::den != 1)) count /= Factor::den;
|
||||||
if (Factor::den != 1) {
|
auto tocount = lossless_integral_conversion<typename To::rep>(count, ec);
|
||||||
count /= Factor::den;
|
return ec ? To() : To(tocount);
|
||||||
}
|
|
||||||
// convert to the to type, safely
|
|
||||||
using ToRep = typename To::rep;
|
|
||||||
const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
|
|
||||||
if (ec) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return To{tocount};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -269,7 +244,7 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|||||||
|
|
||||||
// multiply with Factor::num without overflow or underflow
|
// multiply with Factor::num without overflow or underflow
|
||||||
if (Factor::num != 1) {
|
if (Factor::num != 1) {
|
||||||
constexpr auto max1 = internal::max_value<IntermediateRep>() /
|
constexpr auto max1 = detail::max_value<IntermediateRep>() /
|
||||||
static_cast<IntermediateRep>(Factor::num);
|
static_cast<IntermediateRep>(Factor::num);
|
||||||
if (count > max1) {
|
if (count > max1) {
|
||||||
ec = 1;
|
ec = 1;
|
||||||
@@ -306,14 +281,90 @@ To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
|
|||||||
// Usage: f FMT_NOMACRO()
|
// Usage: f FMT_NOMACRO()
|
||||||
#define FMT_NOMACRO
|
#define FMT_NOMACRO
|
||||||
|
|
||||||
namespace internal {
|
namespace detail {
|
||||||
|
template <typename T = void> struct null {};
|
||||||
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
|
||||||
inline null<> localtime_s(...) { return null<>(); }
|
inline null<> localtime_s(...) { return null<>(); }
|
||||||
inline null<> gmtime_r(...) { return null<>(); }
|
inline null<> gmtime_r(...) { return null<>(); }
|
||||||
inline null<> gmtime_s(...) { return null<>(); }
|
inline null<> gmtime_s(...) { return null<>(); }
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
// Thread-safe replacement for std::localtime
|
inline auto do_write(const std::tm& time, const std::locale& loc, char format,
|
||||||
|
char modifier) -> std::string {
|
||||||
|
auto&& os = std::ostringstream();
|
||||||
|
os.imbue(loc);
|
||||||
|
using iterator = std::ostreambuf_iterator<char>;
|
||||||
|
const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
|
||||||
|
auto end = facet.put(os, os, ' ', &time, format, modifier);
|
||||||
|
if (end.failed()) FMT_THROW(format_error("failed to format time"));
|
||||||
|
auto str = os.str();
|
||||||
|
if (!detail::is_utf8() || loc == std::locale::classic()) return str;
|
||||||
|
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
|
||||||
|
// gcc-4.
|
||||||
|
#if FMT_MSC_VER != 0 || \
|
||||||
|
(defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
|
||||||
|
// The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
|
||||||
|
// and newer.
|
||||||
|
using code_unit = wchar_t;
|
||||||
|
#else
|
||||||
|
using code_unit = char32_t;
|
||||||
|
#endif
|
||||||
|
auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
|
||||||
|
auto mb = std::mbstate_t();
|
||||||
|
const char* from_next = nullptr;
|
||||||
|
code_unit* to_next = nullptr;
|
||||||
|
constexpr size_t buf_size = 32;
|
||||||
|
code_unit buf[buf_size] = {};
|
||||||
|
auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
|
||||||
|
buf + buf_size, to_next);
|
||||||
|
if (result != std::codecvt_base::ok)
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
str.clear();
|
||||||
|
for (code_unit* p = buf; p != to_next; ++p) {
|
||||||
|
uint32_t c = static_cast<uint32_t>(*p);
|
||||||
|
if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
|
||||||
|
// surrogate pair
|
||||||
|
++p;
|
||||||
|
if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
}
|
||||||
|
c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
|
||||||
|
}
|
||||||
|
if (c < 0x80) {
|
||||||
|
str.push_back(static_cast<char>(c));
|
||||||
|
} else if (c < 0x800) {
|
||||||
|
str.push_back(static_cast<char>(0xc0 | (c >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
|
||||||
|
str.push_back(static_cast<char>(0xe0 | (c >> 12)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else if (c >= 0x10000 && c <= 0x10ffff) {
|
||||||
|
str.push_back(static_cast<char>(0xf0 | (c >> 18)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
|
||||||
|
str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
|
||||||
|
} else {
|
||||||
|
FMT_THROW(format_error("failed to format time"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
|
||||||
|
char format, char modifier = 0) -> OutputIt {
|
||||||
|
auto str = do_write(time, loc, format, modifier);
|
||||||
|
return std::copy(str.begin(), str.end(), out);
|
||||||
|
}
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
||||||
|
expressed in local time. Unlike ``std::localtime``, this function is
|
||||||
|
thread-safe on most platforms.
|
||||||
|
*/
|
||||||
inline std::tm localtime(std::time_t time) {
|
inline std::tm localtime(std::time_t time) {
|
||||||
struct dispatcher {
|
struct dispatcher {
|
||||||
std::time_t time_;
|
std::time_t time_;
|
||||||
@@ -322,22 +373,22 @@ inline std::tm localtime(std::time_t time) {
|
|||||||
dispatcher(std::time_t t) : time_(t) {}
|
dispatcher(std::time_t t) : time_(t) {}
|
||||||
|
|
||||||
bool run() {
|
bool run() {
|
||||||
using namespace fmt::internal;
|
using namespace fmt::detail;
|
||||||
return handle(localtime_r(&time_, &tm_));
|
return handle(localtime_r(&time_, &tm_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||||
|
|
||||||
bool handle(internal::null<>) {
|
bool handle(detail::null<>) {
|
||||||
using namespace fmt::internal;
|
using namespace fmt::detail;
|
||||||
return fallback(localtime_s(&tm_, &time_));
|
return fallback(localtime_s(&tm_, &time_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fallback(int res) { return res == 0; }
|
bool fallback(int res) { return res == 0; }
|
||||||
|
|
||||||
#if !FMT_MSC_VER
|
#if !FMT_MSC_VER
|
||||||
bool fallback(internal::null<>) {
|
bool fallback(detail::null<>) {
|
||||||
using namespace fmt::internal;
|
using namespace fmt::detail;
|
||||||
std::tm* tm = std::localtime(&time_);
|
std::tm* tm = std::localtime(&time_);
|
||||||
if (tm) tm_ = *tm;
|
if (tm) tm_ = *tm;
|
||||||
return tm != nullptr;
|
return tm != nullptr;
|
||||||
@@ -350,7 +401,16 @@ inline std::tm localtime(std::time_t time) {
|
|||||||
return lt.tm_;
|
return lt.tm_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thread-safe replacement for std::gmtime
|
inline std::tm localtime(
|
||||||
|
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||||
|
return localtime(std::chrono::system_clock::to_time_t(time_point));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts given time since epoch as ``std::time_t`` value into calendar time,
|
||||||
|
expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this
|
||||||
|
function is thread-safe on most platforms.
|
||||||
|
*/
|
||||||
inline std::tm gmtime(std::time_t time) {
|
inline std::tm gmtime(std::time_t time) {
|
||||||
struct dispatcher {
|
struct dispatcher {
|
||||||
std::time_t time_;
|
std::time_t time_;
|
||||||
@@ -359,21 +419,21 @@ inline std::tm gmtime(std::time_t time) {
|
|||||||
dispatcher(std::time_t t) : time_(t) {}
|
dispatcher(std::time_t t) : time_(t) {}
|
||||||
|
|
||||||
bool run() {
|
bool run() {
|
||||||
using namespace fmt::internal;
|
using namespace fmt::detail;
|
||||||
return handle(gmtime_r(&time_, &tm_));
|
return handle(gmtime_r(&time_, &tm_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handle(std::tm* tm) { return tm != nullptr; }
|
bool handle(std::tm* tm) { return tm != nullptr; }
|
||||||
|
|
||||||
bool handle(internal::null<>) {
|
bool handle(detail::null<>) {
|
||||||
using namespace fmt::internal;
|
using namespace fmt::detail;
|
||||||
return fallback(gmtime_s(&tm_, &time_));
|
return fallback(gmtime_s(&tm_, &time_));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fallback(int res) { return res == 0; }
|
bool fallback(int res) { return res == 0; }
|
||||||
|
|
||||||
#if !FMT_MSC_VER
|
#if !FMT_MSC_VER
|
||||||
bool fallback(internal::null<>) {
|
bool fallback(detail::null<>) {
|
||||||
std::tm* tm = std::gmtime(&time_);
|
std::tm* tm = std::gmtime(&time_);
|
||||||
if (tm) tm_ = *tm;
|
if (tm) tm_ = *tm;
|
||||||
return tm != nullptr;
|
return tm != nullptr;
|
||||||
@@ -386,86 +446,132 @@ inline std::tm gmtime(std::time_t time) {
|
|||||||
return gt.tm_;
|
return gt.tm_;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace internal {
|
inline std::tm gmtime(
|
||||||
inline std::size_t strftime(char* str, std::size_t count, const char* format,
|
std::chrono::time_point<std::chrono::system_clock> time_point) {
|
||||||
|
return gmtime(std::chrono::system_clock::to_time_t(time_point));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
inline size_t strftime(char* str, size_t count, const char* format,
|
||||||
const std::tm* time) {
|
const std::tm* time) {
|
||||||
return std::strftime(str, count, format, time);
|
// Assign to a pointer to suppress GCCs -Wformat-nonliteral
|
||||||
|
// First assign the nullptr to suppress -Wsuggest-attribute=format
|
||||||
|
std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
|
||||||
|
nullptr;
|
||||||
|
strftime = std::strftime;
|
||||||
|
return strftime(str, count, format, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::size_t strftime(wchar_t* str, std::size_t count,
|
inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
|
||||||
const wchar_t* format, const std::tm* time) {
|
const std::tm* time) {
|
||||||
return std::wcsftime(str, count, format, time);
|
// See above
|
||||||
|
std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
|
||||||
|
const std::tm*) = nullptr;
|
||||||
|
wcsftime = std::wcsftime;
|
||||||
|
return wcsftime(str, count, format, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
template <typename Char, typename Duration>
|
||||||
|
struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||||
|
Char> : formatter<std::tm, Char> {
|
||||||
|
FMT_CONSTEXPR formatter() {
|
||||||
|
this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
|
||||||
}
|
}
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
template <typename Char> struct formatter<std::tm, Char> {
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
auto it = ctx.begin();
|
auto it = ctx.begin();
|
||||||
if (it != ctx.end() && *it == ':') ++it;
|
if (it != ctx.end() && *it == ':') ++it;
|
||||||
auto end = it;
|
auto end = it;
|
||||||
while (end != ctx.end() && *end != '}') ++end;
|
while (end != ctx.end() && *end != '}') ++end;
|
||||||
tm_format.reserve(internal::to_unsigned(end - it + 1));
|
if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
|
||||||
tm_format.append(it, end);
|
|
||||||
tm_format.push_back('\0');
|
|
||||||
return end;
|
return end;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(std::chrono::time_point<std::chrono::system_clock> val,
|
||||||
|
FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
|
std::tm time = localtime(val);
|
||||||
|
return formatter<std::tm, Char>::format(time, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
|
||||||
|
'%', 'd', ' ', '%', 'H', ':',
|
||||||
|
'%', 'M', ':', '%', 'S'};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename Duration>
|
||||||
|
constexpr Char
|
||||||
|
formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
|
||||||
|
Char>::default_specs[];
|
||||||
|
|
||||||
|
template <typename Char> struct formatter<std::tm, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto it = ctx.begin();
|
||||||
|
if (it != ctx.end() && *it == ':') ++it;
|
||||||
|
auto end = it;
|
||||||
|
while (end != ctx.end() && *end != '}') ++end;
|
||||||
|
specs = {it, detail::to_unsigned(end - it)};
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
auto format(const std::tm& tm, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
basic_memory_buffer<Char> tm_format;
|
||||||
|
tm_format.append(specs.begin(), specs.end());
|
||||||
|
// By appending an extra space we can distinguish an empty result that
|
||||||
|
// indicates insufficient buffer size from a guaranteed non-empty result
|
||||||
|
// https://github.com/fmtlib/fmt/issues/2238
|
||||||
|
tm_format.push_back(' ');
|
||||||
|
tm_format.push_back('\0');
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
std::size_t start = buf.size();
|
size_t start = buf.size();
|
||||||
for (;;) {
|
for (;;) {
|
||||||
std::size_t size = buf.capacity() - start;
|
size_t size = buf.capacity() - start;
|
||||||
std::size_t count =
|
size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
|
||||||
internal::strftime(&buf[start], size, &tm_format[0], &tm);
|
|
||||||
if (count != 0) {
|
if (count != 0) {
|
||||||
buf.resize(start + count);
|
buf.resize(start + count);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (size >= tm_format.size() * 256) {
|
const size_t MIN_GROWTH = 10;
|
||||||
// If the buffer is 256 times larger than the format string, assume
|
|
||||||
// that `strftime` gives an empty result. There doesn't seem to be a
|
|
||||||
// better way to distinguish the two cases:
|
|
||||||
// https://github.com/fmtlib/fmt/issues/367
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const std::size_t MIN_GROWTH = 10;
|
|
||||||
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
|
||||||
}
|
}
|
||||||
return std::copy(buf.begin(), buf.end(), ctx.out());
|
// Remove the extra space.
|
||||||
|
return std::copy(buf.begin(), buf.end() - 1, ctx.out());
|
||||||
}
|
}
|
||||||
|
|
||||||
basic_memory_buffer<Char> tm_format;
|
basic_string_view<Char> specs;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace internal {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
template <typename Period> FMT_CONSTEXPR const char* get_units() {
|
|
||||||
|
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
|
||||||
|
if (std::is_same<Period, std::atto>::value) return "as";
|
||||||
|
if (std::is_same<Period, std::femto>::value) return "fs";
|
||||||
|
if (std::is_same<Period, std::pico>::value) return "ps";
|
||||||
|
if (std::is_same<Period, std::nano>::value) return "ns";
|
||||||
|
if (std::is_same<Period, std::micro>::value) return "µs";
|
||||||
|
if (std::is_same<Period, std::milli>::value) return "ms";
|
||||||
|
if (std::is_same<Period, std::centi>::value) return "cs";
|
||||||
|
if (std::is_same<Period, std::deci>::value) return "ds";
|
||||||
|
if (std::is_same<Period, std::ratio<1>>::value) return "s";
|
||||||
|
if (std::is_same<Period, std::deca>::value) return "das";
|
||||||
|
if (std::is_same<Period, std::hecto>::value) return "hs";
|
||||||
|
if (std::is_same<Period, std::kilo>::value) return "ks";
|
||||||
|
if (std::is_same<Period, std::mega>::value) return "Ms";
|
||||||
|
if (std::is_same<Period, std::giga>::value) return "Gs";
|
||||||
|
if (std::is_same<Period, std::tera>::value) return "Ts";
|
||||||
|
if (std::is_same<Period, std::peta>::value) return "Ps";
|
||||||
|
if (std::is_same<Period, std::exa>::value) return "Es";
|
||||||
|
if (std::is_same<Period, std::ratio<60>>::value) return "m";
|
||||||
|
if (std::is_same<Period, std::ratio<3600>>::value) return "h";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; }
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() {
|
|
||||||
return "m";
|
|
||||||
}
|
|
||||||
template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() {
|
|
||||||
return "h";
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class numeric_system {
|
enum class numeric_system {
|
||||||
standard,
|
standard,
|
||||||
@@ -631,33 +737,50 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin,
|
|||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct chrono_format_checker {
|
template <typename Derived> struct null_chrono_spec_handler {
|
||||||
FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); }
|
FMT_CONSTEXPR void unsupported() {
|
||||||
|
static_cast<Derived*>(this)->unsupported();
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_full_month() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_us_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_date() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_iso_time() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_am_pm() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_value() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_duration_unit() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_utc_offset() { unsupported(); }
|
||||||
|
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char> void on_text(const Char*, const Char*) {}
|
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
|
||||||
FMT_NORETURN void on_abbr_weekday() { report_no_date(); }
|
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
|
||||||
FMT_NORETURN void on_full_weekday() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); }
|
template <typename Char>
|
||||||
FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
|
||||||
FMT_NORETURN void on_abbr_month() { report_no_date(); }
|
FMT_CONSTEXPR void on_24_hour(numeric_system) {}
|
||||||
FMT_NORETURN void on_full_month() { report_no_date(); }
|
FMT_CONSTEXPR void on_12_hour(numeric_system) {}
|
||||||
void on_24_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_minute(numeric_system) {}
|
||||||
void on_12_hour(numeric_system) {}
|
FMT_CONSTEXPR void on_second(numeric_system) {}
|
||||||
void on_minute(numeric_system) {}
|
FMT_CONSTEXPR void on_12_hour_time() {}
|
||||||
void on_second(numeric_system) {}
|
FMT_CONSTEXPR void on_24_hour_time() {}
|
||||||
FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_iso_time() {}
|
||||||
FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_am_pm() {}
|
||||||
FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); }
|
FMT_CONSTEXPR void on_duration_value() {}
|
||||||
FMT_NORETURN void on_us_date() { report_no_date(); }
|
FMT_CONSTEXPR void on_duration_unit() {}
|
||||||
FMT_NORETURN void on_iso_date() { report_no_date(); }
|
|
||||||
void on_12_hour_time() {}
|
|
||||||
void on_24_hour_time() {}
|
|
||||||
void on_iso_time() {}
|
|
||||||
void on_am_pm() {}
|
|
||||||
void on_duration_value() {}
|
|
||||||
void on_duration_unit() {}
|
|
||||||
FMT_NORETURN void on_utc_offset() { report_no_date(); }
|
|
||||||
FMT_NORETURN void on_tz_name() { report_no_date(); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
@@ -681,7 +804,8 @@ inline bool isfinite(T value) {
|
|||||||
// Converts value to int and checks that it's in the range [0, upper).
|
// Converts value to int and checks that it's in the range [0, upper).
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
inline int to_nonnegative_int(T value, int upper) {
|
inline int to_nonnegative_int(T value, int upper) {
|
||||||
FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
|
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
|
||||||
|
"invalid value");
|
||||||
(void)upper;
|
(void)upper;
|
||||||
return static_cast<int>(value);
|
return static_cast<int>(value);
|
||||||
}
|
}
|
||||||
@@ -759,30 +883,47 @@ inline std::chrono::duration<Rep, std::milli> get_milliseconds(
|
|||||||
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename Rep, typename OutputIt>
|
template <typename Char, typename Rep, typename OutputIt,
|
||||||
|
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
|
||||||
|
OutputIt format_duration_value(OutputIt out, Rep val, int) {
|
||||||
|
return write<Char>(out, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename Rep, typename OutputIt,
|
||||||
|
FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
|
||||||
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
|
||||||
const Char pr_f[] = {'{', ':', '.', '{', '}', 'f', '}', 0};
|
auto specs = basic_format_specs<Char>();
|
||||||
if (precision >= 0) return format_to(out, pr_f, val, precision);
|
specs.precision = precision;
|
||||||
const Char fp_f[] = {'{', ':', 'g', '}', 0};
|
specs.type = precision > 0 ? 'f' : 'g';
|
||||||
const Char format[] = {'{', '}', 0};
|
return write<Char>(out, val, specs);
|
||||||
return format_to(out, std::is_floating_point<Rep>::value ? fp_f : format,
|
}
|
||||||
val);
|
|
||||||
|
template <typename Char, typename OutputIt>
|
||||||
|
OutputIt copy_unit(string_view unit, OutputIt out, Char) {
|
||||||
|
return std::copy(unit.begin(), unit.end(), out);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) {
|
||||||
|
// This works when wchar_t is UTF-32 because units only contain characters
|
||||||
|
// that have the same representation in UTF-16 and UTF-32.
|
||||||
|
utf8_to_utf16 u(unit);
|
||||||
|
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename Period, typename OutputIt>
|
template <typename Char, typename Period, typename OutputIt>
|
||||||
OutputIt format_duration_unit(OutputIt out) {
|
OutputIt format_duration_unit(OutputIt out) {
|
||||||
if (const char* unit = get_units<Period>()) {
|
if (const char* unit = get_units<Period>())
|
||||||
string_view s(unit);
|
return copy_unit(string_view(unit), out, Char());
|
||||||
if (const_check(std::is_same<Char, wchar_t>())) {
|
*out++ = '[';
|
||||||
utf8_to_utf16 u(s);
|
out = write<Char>(out, Period::num);
|
||||||
return std::copy(u.c_str(), u.c_str() + u.size(), out);
|
if (const_check(Period::den != 1)) {
|
||||||
|
*out++ = '/';
|
||||||
|
out = write<Char>(out, Period::den);
|
||||||
}
|
}
|
||||||
return std::copy(s.begin(), s.end(), out);
|
*out++ = ']';
|
||||||
}
|
*out++ = 's';
|
||||||
const Char num_f[] = {'[', '{', '}', ']', 's', 0};
|
return out;
|
||||||
if (Period::den == 1) return format_to(out, num_f, Period::num);
|
|
||||||
const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0};
|
|
||||||
return format_to(out, num_def_f, Period::num, Period::den);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext, typename OutputIt, typename Rep,
|
template <typename FormatContext, typename OutputIt, typename Rep,
|
||||||
@@ -791,6 +932,7 @@ struct chrono_formatter {
|
|||||||
FormatContext& context;
|
FormatContext& context;
|
||||||
OutputIt out;
|
OutputIt out;
|
||||||
int precision;
|
int precision;
|
||||||
|
bool localized = false;
|
||||||
// rep is unsigned to avoid overflow.
|
// rep is unsigned to avoid overflow.
|
||||||
using rep =
|
using rep =
|
||||||
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
|
||||||
@@ -874,9 +1016,9 @@ struct chrono_formatter {
|
|||||||
if (isnan(value)) return write_nan();
|
if (isnan(value)) return write_nan();
|
||||||
uint32_or_64_or_128_t<int> n =
|
uint32_or_64_or_128_t<int> n =
|
||||||
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
to_unsigned(to_nonnegative_int(value, max_value<int>()));
|
||||||
int num_digits = internal::count_digits(n);
|
int num_digits = detail::count_digits(n);
|
||||||
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
|
||||||
out = format_decimal<char_type>(out, n, num_digits);
|
out = format_decimal<char_type>(out, n, num_digits).end;
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_nan() { std::copy_n("nan", 3, out); }
|
void write_nan() { std::copy_n("nan", 3, out); }
|
||||||
@@ -885,13 +1027,9 @@ struct chrono_formatter {
|
|||||||
|
|
||||||
void format_localized(const tm& time, char format, char modifier = 0) {
|
void format_localized(const tm& time, char format, char modifier = 0) {
|
||||||
if (isnan(val)) return write_nan();
|
if (isnan(val)) return write_nan();
|
||||||
auto locale = context.locale().template get<std::locale>();
|
const auto& loc = localized ? context.locale().template get<std::locale>()
|
||||||
auto& facet = std::use_facet<std::time_put<char_type>>(locale);
|
: std::locale::classic();
|
||||||
std::basic_ostringstream<char_type> os;
|
out = detail::write(out, time, loc, format, modifier);
|
||||||
os.imbue(locale);
|
|
||||||
facet.put(os, os, ' ', &time, format, modifier);
|
|
||||||
auto str = os.str();
|
|
||||||
std::copy(str.begin(), str.end(), out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_text(const char_type* begin, const char_type* end) {
|
void on_text(const char_type* begin, const char_type* end) {
|
||||||
@@ -1004,17 +1142,59 @@ struct chrono_formatter {
|
|||||||
out = format_duration_unit<char_type, Period>(out);
|
out = format_duration_unit<char_type, Period>(out);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace internal
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907
|
||||||
|
using weekday = std::chrono::weekday;
|
||||||
|
#else
|
||||||
|
// A fallback version of weekday.
|
||||||
|
class weekday {
|
||||||
|
private:
|
||||||
|
unsigned char value;
|
||||||
|
|
||||||
|
public:
|
||||||
|
weekday() = default;
|
||||||
|
explicit constexpr weekday(unsigned wd) noexcept
|
||||||
|
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
|
||||||
|
constexpr unsigned c_encoding() const noexcept { return value; }
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A rudimentary weekday formatter.
|
||||||
|
template <> struct formatter<weekday> {
|
||||||
|
private:
|
||||||
|
bool localized = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
|
||||||
|
auto begin = ctx.begin(), end = ctx.end();
|
||||||
|
if (begin != end && *begin == 'L') {
|
||||||
|
++begin;
|
||||||
|
localized = true;
|
||||||
|
}
|
||||||
|
return begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
|
||||||
|
auto time = std::tm();
|
||||||
|
time.tm_wday = static_cast<int>(wd.c_encoding());
|
||||||
|
const auto& loc = localized ? ctx.locale().template get<std::locale>()
|
||||||
|
: std::locale::classic();
|
||||||
|
return detail::write(ctx.out(), time, loc, 'a');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Rep, typename Period, typename Char>
|
template <typename Rep, typename Period, typename Char>
|
||||||
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
||||||
private:
|
private:
|
||||||
basic_format_specs<Char> specs;
|
basic_format_specs<Char> specs;
|
||||||
int precision;
|
int precision = -1;
|
||||||
using arg_ref_type = internal::arg_ref<Char>;
|
using arg_ref_type = detail::arg_ref<Char>;
|
||||||
arg_ref_type width_ref;
|
arg_ref_type width_ref;
|
||||||
arg_ref_type precision_ref;
|
arg_ref_type precision_ref;
|
||||||
mutable basic_string_view<Char> format_str;
|
bool localized = false;
|
||||||
|
basic_string_view<Char> format_str;
|
||||||
using duration = std::chrono::duration<Rep, Period>;
|
using duration = std::chrono::duration<Rep, Period>;
|
||||||
|
|
||||||
struct spec_handler {
|
struct spec_handler {
|
||||||
@@ -1032,22 +1212,26 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|||||||
return arg_ref_type(arg_id);
|
return arg_ref_type(arg_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
|
FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) {
|
||||||
return arg_ref_type(context.next_arg_id());
|
return arg_ref_type(context.next_arg_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
void on_error(const char* msg) { FMT_THROW(format_error(msg)); }
|
||||||
void on_fill(basic_string_view<Char> fill) { f.specs.fill = fill; }
|
FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) {
|
||||||
void on_align(align_t align) { f.specs.align = align; }
|
f.specs.fill = fill;
|
||||||
void on_width(int width) { f.specs.width = width; }
|
}
|
||||||
void on_precision(int _precision) { f.precision = _precision; }
|
FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; }
|
||||||
void end_precision() {}
|
FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; }
|
||||||
|
FMT_CONSTEXPR void on_precision(int _precision) {
|
||||||
|
f.precision = _precision;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR void end_precision() {}
|
||||||
|
|
||||||
template <typename Id> void on_dynamic_width(Id arg_id) {
|
template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
|
||||||
f.width_ref = make_arg_ref(arg_id);
|
f.width_ref = make_arg_ref(arg_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Id> void on_dynamic_precision(Id arg_id) {
|
template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
|
||||||
f.precision_ref = make_arg_ref(arg_id);
|
f.precision_ref = make_arg_ref(arg_id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1062,58 +1246,63 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
|
|||||||
auto begin = ctx.begin(), end = ctx.end();
|
auto begin = ctx.begin(), end = ctx.end();
|
||||||
if (begin == end || *begin == '}') return {begin, begin};
|
if (begin == end || *begin == '}') return {begin, begin};
|
||||||
spec_handler handler{*this, ctx, format_str};
|
spec_handler handler{*this, ctx, format_str};
|
||||||
begin = internal::parse_align(begin, end, handler);
|
begin = detail::parse_align(begin, end, handler);
|
||||||
if (begin == end) return {begin, begin};
|
if (begin == end) return {begin, begin};
|
||||||
begin = internal::parse_width(begin, end, handler);
|
begin = detail::parse_width(begin, end, handler);
|
||||||
if (begin == end) return {begin, begin};
|
if (begin == end) return {begin, begin};
|
||||||
if (*begin == '.') {
|
if (*begin == '.') {
|
||||||
if (std::is_floating_point<Rep>::value)
|
if (std::is_floating_point<Rep>::value)
|
||||||
begin = internal::parse_precision(begin, end, handler);
|
begin = detail::parse_precision(begin, end, handler);
|
||||||
else
|
else
|
||||||
handler.on_error("precision not allowed for this argument type");
|
handler.on_error("precision not allowed for this argument type");
|
||||||
}
|
}
|
||||||
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
|
if (begin != end && *begin == 'L') {
|
||||||
|
++begin;
|
||||||
|
localized = true;
|
||||||
|
}
|
||||||
|
end = parse_chrono_format(begin, end, detail::chrono_format_checker());
|
||||||
return {begin, end};
|
return {begin, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
formatter() : precision(-1) {}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||||
-> decltype(ctx.begin()) {
|
-> decltype(ctx.begin()) {
|
||||||
auto range = do_parse(ctx);
|
auto range = do_parse(ctx);
|
||||||
format_str = basic_string_view<Char>(
|
format_str = basic_string_view<Char>(
|
||||||
&*range.begin, internal::to_unsigned(range.end - range.begin));
|
&*range.begin, detail::to_unsigned(range.end - range.begin));
|
||||||
return range.end;
|
return range.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(const duration& d, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto specs_copy = specs;
|
||||||
|
auto precision_copy = precision;
|
||||||
auto begin = format_str.begin(), end = format_str.end();
|
auto begin = format_str.begin(), end = format_str.end();
|
||||||
// As a possible future optimization, we could avoid extra copying if width
|
// As a possible future optimization, we could avoid extra copying if width
|
||||||
// is not specified.
|
// is not specified.
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
auto out = std::back_inserter(buf);
|
auto out = std::back_inserter(buf);
|
||||||
using range = internal::output_range<decltype(ctx.out()), Char>;
|
detail::handle_dynamic_spec<detail::width_checker>(specs_copy.width,
|
||||||
internal::basic_writer<range> w(range(ctx.out()));
|
|
||||||
internal::handle_dynamic_spec<internal::width_checker>(specs.width,
|
|
||||||
width_ref, ctx);
|
width_ref, ctx);
|
||||||
internal::handle_dynamic_spec<internal::precision_checker>(
|
detail::handle_dynamic_spec<detail::precision_checker>(precision_copy,
|
||||||
precision, precision_ref, ctx);
|
precision_ref, ctx);
|
||||||
if (begin == end || *begin == '}') {
|
if (begin == end || *begin == '}') {
|
||||||
out = internal::format_duration_value<Char>(out, d.count(), precision);
|
out = detail::format_duration_value<Char>(out, d.count(), precision_copy);
|
||||||
internal::format_duration_unit<Char, Period>(out);
|
detail::format_duration_unit<Char, Period>(out);
|
||||||
} else {
|
} else {
|
||||||
internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period> f(
|
||||||
ctx, out, d);
|
ctx, out, d);
|
||||||
f.precision = precision;
|
f.precision = precision_copy;
|
||||||
parse_chrono_format(begin, end, f);
|
f.localized = localized;
|
||||||
|
detail::parse_chrono_format(begin, end, f);
|
||||||
}
|
}
|
||||||
w.write(buf.data(), buf.size(), specs);
|
return detail::write(
|
||||||
return w.out();
|
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs_copy);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_CHRONO_H_
|
#endif // FMT_CHRONO_H_
|
||||||
|
|||||||
@@ -10,7 +10,15 @@
|
|||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
|
// __declspec(deprecated) is broken in some MSVC versions.
|
||||||
|
#if FMT_MSC_VER
|
||||||
|
# define FMT_DEPRECATED_NONMSVC
|
||||||
|
#else
|
||||||
|
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
|
||||||
|
#endif
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
enum class color : uint32_t {
|
enum class color : uint32_t {
|
||||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||||
@@ -198,7 +206,7 @@ struct rgb {
|
|||||||
uint8_t b;
|
uint8_t b;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace internal {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
// color is a struct of either a rgb color or a terminal color.
|
// color is a struct of either a rgb color or a terminal color.
|
||||||
struct color_type {
|
struct color_type {
|
||||||
@@ -221,9 +229,10 @@ struct color_type {
|
|||||||
uint32_t rgb_color;
|
uint32_t rgb_color;
|
||||||
} value;
|
} value;
|
||||||
};
|
};
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
// Experimental text formatting support.
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
/** A text style consisting of foreground and background colors and emphasis. */
|
||||||
class text_style {
|
class text_style {
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||||
@@ -260,7 +269,55 @@ class text_style {
|
|||||||
return lhs |= rhs;
|
return lhs |= rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
|
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
|
||||||
|
const text_style& rhs) {
|
||||||
|
return and_assign(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
|
||||||
|
operator&(text_style lhs, const text_style& rhs) {
|
||||||
|
return lhs.and_assign(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||||
|
return set_foreground_color;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||||
|
return set_background_color;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||||
|
return static_cast<uint8_t>(ems) != 0;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||||
|
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||||
|
return foreground_color;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||||
|
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||||
|
return background_color;
|
||||||
|
}
|
||||||
|
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||||
|
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||||
|
return ems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||||
|
detail::color_type text_color) FMT_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEPRECATED!
|
||||||
|
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
|
||||||
if (!set_foreground_color) {
|
if (!set_foreground_color) {
|
||||||
set_foreground_color = rhs.set_foreground_color;
|
set_foreground_color = rhs.set_foreground_color;
|
||||||
foreground_color = rhs.foreground_color;
|
foreground_color = rhs.foreground_color;
|
||||||
@@ -284,87 +341,49 @@ class text_style {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
|
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||||
const text_style& rhs) {
|
|
||||||
return lhs &= rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
|
||||||
return set_foreground_color;
|
|
||||||
}
|
|
||||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
|
||||||
return set_background_color;
|
|
||||||
}
|
|
||||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
|
||||||
return static_cast<uint8_t>(ems) != 0;
|
|
||||||
}
|
|
||||||
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
|
|
||||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
|
||||||
return foreground_color;
|
|
||||||
}
|
|
||||||
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
|
|
||||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
|
||||||
return background_color;
|
|
||||||
}
|
|
||||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
|
||||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
|
||||||
return ems;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
|
||||||
internal::color_type text_color) FMT_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_DECL text_style fg(internal::color_type foreground)
|
|
||||||
FMT_NOEXCEPT;
|
|
||||||
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
|
|
||||||
FMT_NOEXCEPT;
|
FMT_NOEXCEPT;
|
||||||
|
|
||||||
internal::color_type foreground_color;
|
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||||
internal::color_type background_color;
|
FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
detail::color_type foreground_color;
|
||||||
|
detail::color_type background_color;
|
||||||
bool set_foreground_color;
|
bool set_foreground_color;
|
||||||
bool set_background_color;
|
bool set_background_color;
|
||||||
emphasis ems;
|
emphasis ems;
|
||||||
};
|
};
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
|
/** Creates a text style from the foreground (text) color. */
|
||||||
return text_style(/*is_foreground=*/true, foreground);
|
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||||
|
return text_style(true, foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
|
/** Creates a text style from the background color. */
|
||||||
return text_style(/*is_foreground=*/false, background);
|
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||||
|
return text_style(false, background);
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
|
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
|
||||||
|
emphasis rhs) FMT_NOEXCEPT {
|
||||||
return text_style(lhs) | rhs;
|
return text_style(lhs) | rhs;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace internal {
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename Char> struct ansi_color_escape {
|
template <typename Char> struct ansi_color_escape {
|
||||||
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
|
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||||
const char* esc) FMT_NOEXCEPT {
|
const char* esc) FMT_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_rgb) {
|
||||||
bool is_background = esc == internal::data::background_color;
|
bool is_background = esc == string_view("\x1b[48;2;");
|
||||||
uint32_t value = text_color.value.term_color;
|
uint32_t value = text_color.value.term_color;
|
||||||
// Background ASCII codes are the same as the foreground ones but with
|
// Background ASCII codes are the same as the foreground ones but with
|
||||||
// 10 more.
|
// 10 more.
|
||||||
if (is_background) value += 10u;
|
if (is_background) value += 10u;
|
||||||
|
|
||||||
std::size_t index = 0;
|
size_t index = 0;
|
||||||
buffer[index++] = static_cast<Char>('\x1b');
|
buffer[index++] = static_cast<Char>('\x1b');
|
||||||
buffer[index++] = static_cast<Char>('[');
|
buffer[index++] = static_cast<Char>('[');
|
||||||
|
|
||||||
@@ -398,7 +417,7 @@ template <typename Char> struct ansi_color_escape {
|
|||||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||||
em_codes[3] = 9;
|
em_codes[3] = 9;
|
||||||
|
|
||||||
std::size_t index = 0;
|
size_t index = 0;
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
if (!em_codes[i]) continue;
|
if (!em_codes[i]) continue;
|
||||||
buffer[index++] = static_cast<Char>('\x1b');
|
buffer[index++] = static_cast<Char>('\x1b');
|
||||||
@@ -411,7 +430,7 @@ template <typename Char> struct ansi_color_escape {
|
|||||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||||
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
|
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
|
||||||
return buffer + std::char_traits<Char>::length(buffer);
|
return buffer + std::char_traits<Char>::length(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,14 +448,14 @@ template <typename Char> struct ansi_color_escape {
|
|||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||||
internal::color_type foreground) FMT_NOEXCEPT {
|
detail::color_type foreground) FMT_NOEXCEPT {
|
||||||
return ansi_color_escape<Char>(foreground, internal::data::foreground_color);
|
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||||
internal::color_type background) FMT_NOEXCEPT {
|
detail::color_type background) FMT_NOEXCEPT {
|
||||||
return ansi_color_escape<Char>(background, internal::data::background_color);
|
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
@@ -455,82 +474,86 @@ inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||||
fputs(internal::data::reset_color, stream);
|
fputs("\x1b[0m", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||||
fputs(internal::data::wreset_color, stream);
|
fputs(L"\x1b[0m", stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
|
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||||
const char* begin = data::reset_color;
|
auto reset_color = string_view("\x1b[0m");
|
||||||
const char* end = begin + sizeof(data::reset_color) - 1;
|
buffer.append(reset_color.begin(), reset_color.end());
|
||||||
buffer.append(begin, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
|
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||||
basic_string_view<Char> format_str,
|
basic_string_view<Char> format_str,
|
||||||
basic_format_args<buffer_context<Char>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
bool has_style = false;
|
bool has_style = false;
|
||||||
if (ts.has_emphasis()) {
|
if (ts.has_emphasis()) {
|
||||||
has_style = true;
|
has_style = true;
|
||||||
auto emphasis = internal::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;
|
has_style = true;
|
||||||
auto foreground =
|
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||||
internal::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;
|
has_style = true;
|
||||||
auto background =
|
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||||
internal::make_background_color<Char>(ts.get_background());
|
|
||||||
buf.append(background.begin(), background.end());
|
buf.append(background.begin(), background.end());
|
||||||
}
|
}
|
||||||
internal::vformat_to(buf, format_str, args);
|
detail::vformat_to(buf, format_str, args, {});
|
||||||
if (has_style) internal::reset_color<Char>(buf);
|
if (has_style) detail::reset_color<Char>(buf);
|
||||||
}
|
}
|
||||||
} // namespace internal
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||||
basic_format_args<buffer_context<Char>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
internal::vformat_to(buf, ts, to_string_view(format), args);
|
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||||
buf.push_back(Char(0));
|
buf.push_back(Char(0));
|
||||||
internal::fputs(buf.data(), f);
|
detail::fputs(buf.data(), f);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
\rst
|
||||||
Formats a string and prints it to the specified file stream using ANSI
|
Formats a string and prints it to the specified file stream using ANSI
|
||||||
escape sequences to specify text formatting.
|
escape sequences to specify text formatting.
|
||||||
Example:
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
internal::check_format_string<Args...>(format_str);
|
vprint(f, ts, format_str,
|
||||||
using context = buffer_context<char_t<S>>;
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
format_arg_store<context, Args...> as{args...};
|
|
||||||
vprint(f, ts, format_str, basic_format_args<context>(as));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
\rst
|
||||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||||
specify text formatting.
|
specify text formatting.
|
||||||
Example:
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||||
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||||
return print(stdout, ts, format_str, args...);
|
return print(stdout, ts, format_str, args...);
|
||||||
}
|
}
|
||||||
@@ -540,7 +563,7 @@ inline std::basic_string<Char> vformat(
|
|||||||
const text_style& ts, const S& format_str,
|
const text_style& ts, const S& format_str,
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
basic_memory_buffer<Char> buf;
|
basic_memory_buffer<Char> buf;
|
||||||
internal::vformat_to(buf, ts, to_string_view(format_str), args);
|
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||||
return fmt::to_string(buf);
|
return fmt::to_string(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,10 +582,46 @@ inline std::basic_string<Char> vformat(
|
|||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
return vformat(ts, to_string_view(format_str),
|
return fmt::vformat(ts, to_string_view(format_str),
|
||||||
internal::make_args_checked<Args...>(format_str, args...));
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats a string with the given text_style and writes the output to ``out``.
|
||||||
|
*/
|
||||||
|
template <typename OutputIt, typename Char,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||||
|
OutputIt vformat_to(
|
||||||
|
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
detail::vformat_to(buf, ts, format_str, args);
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Formats arguments with the given text_style, writes the result to the output
|
||||||
|
iterator ``out`` and returns the iterator past the end of the output range.
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
std::vector<char> out;
|
||||||
|
fmt::format_to(std::back_inserter(out),
|
||||||
|
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||||
|
detail::is_string<S>::value>
|
||||||
|
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||||
|
Args&&... args) ->
|
||||||
|
typename std::enable_if<enable, OutputIt>::type {
|
||||||
|
return vformat_to(out, ts, to_string_view(format_str),
|
||||||
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COLOR_H_
|
#endif // FMT_COLOR_H_
|
||||||
|
|||||||
@@ -8,333 +8,184 @@
|
|||||||
#ifndef FMT_COMPILE_H_
|
#ifndef FMT_COMPILE_H_
|
||||||
#define FMT_COMPILE_H_
|
#define FMT_COMPILE_H_
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace internal {
|
namespace detail {
|
||||||
|
|
||||||
// Part of a compiled format string. It can be either literal text or a
|
// An output iterator that counts the number of objects written to it and
|
||||||
// replacement field.
|
// discards them.
|
||||||
template <typename Char> struct format_part {
|
class counting_iterator {
|
||||||
enum class kind { arg_index, arg_name, text, replacement };
|
|
||||||
|
|
||||||
struct replacement {
|
|
||||||
arg_ref<Char> arg_id;
|
|
||||||
dynamic_format_specs<Char> specs;
|
|
||||||
};
|
|
||||||
|
|
||||||
kind part_kind;
|
|
||||||
union value {
|
|
||||||
int arg_index;
|
|
||||||
basic_string_view<Char> str;
|
|
||||||
replacement repl;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR value(int index = 0) : arg_index(index) {}
|
|
||||||
FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
|
|
||||||
FMT_CONSTEXPR value(replacement r) : repl(r) {}
|
|
||||||
} val;
|
|
||||||
// Position past the end of the argument id.
|
|
||||||
const Char* arg_id_end = nullptr;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
|
||||||
: part_kind(k), val(v) {}
|
|
||||||
|
|
||||||
static FMT_CONSTEXPR format_part make_arg_index(int index) {
|
|
||||||
return format_part(kind::arg_index, index);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
|
|
||||||
return format_part(kind::arg_name, name);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
|
|
||||||
return format_part(kind::text, text);
|
|
||||||
}
|
|
||||||
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
|
||||||
return format_part(kind::replacement, repl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Char> struct part_counter {
|
|
||||||
unsigned num_parts = 0;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
|
||||||
if (begin != end) ++num_parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_arg_id() { ++num_parts; }
|
|
||||||
FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; }
|
|
||||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; }
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_replacement_field(const Char*) {}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
|
||||||
const Char* end) {
|
|
||||||
// Find the matching brace.
|
|
||||||
unsigned brace_counter = 0;
|
|
||||||
for (; begin != end; ++begin) {
|
|
||||||
if (*begin == '{') {
|
|
||||||
++brace_counter;
|
|
||||||
} else if (*begin == '}') {
|
|
||||||
if (brace_counter == 0u) break;
|
|
||||||
--brace_counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_error(const char*) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Counts the number of parts in a format string.
|
|
||||||
template <typename Char>
|
|
||||||
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
|
||||||
part_counter<Char> counter;
|
|
||||||
parse_format_string<true>(format_str, counter);
|
|
||||||
return counter.num_parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char, typename PartHandler>
|
|
||||||
class format_string_compiler : public error_handler {
|
|
||||||
private:
|
private:
|
||||||
using part = format_part<Char>;
|
size_t count_;
|
||||||
|
|
||||||
PartHandler handler_;
|
|
||||||
part part_;
|
|
||||||
basic_string_view<Char> format_str_;
|
|
||||||
basic_format_parse_context<Char> parse_context_;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
using iterator_category = std::output_iterator_tag;
|
||||||
PartHandler handler)
|
using difference_type = std::ptrdiff_t;
|
||||||
: handler_(handler),
|
using pointer = void;
|
||||||
format_str_(format_str),
|
using reference = void;
|
||||||
parse_context_(format_str) {}
|
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
struct value_type {
|
||||||
if (begin != end)
|
template <typename T> void operator=(const T&) {}
|
||||||
handler_(part::make_text({begin, to_unsigned(end - begin)}));
|
};
|
||||||
|
|
||||||
|
counting_iterator() : count_(0) {}
|
||||||
|
|
||||||
|
size_t count() const { return count_; }
|
||||||
|
|
||||||
|
counting_iterator& operator++() {
|
||||||
|
++count_;
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
counting_iterator operator++(int) {
|
||||||
FMT_CONSTEXPR void on_arg_id() {
|
auto it = *this;
|
||||||
part_ = part::make_arg_index(parse_context_.next_arg_id());
|
++*this;
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_arg_id(int id) {
|
|
||||||
parse_context_.check_arg_id(id);
|
|
||||||
part_ = part::make_arg_index(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
|
||||||
part_ = part::make_arg_name(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
|
||||||
part_.arg_id_end = ptr;
|
|
||||||
handler_(part_);
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
|
||||||
const Char* end) {
|
|
||||||
auto repl = typename part::replacement();
|
|
||||||
dynamic_specs_handler<basic_format_parse_context<Char>> handler(
|
|
||||||
repl.specs, parse_context_);
|
|
||||||
auto it = parse_format_specs(begin, end, handler);
|
|
||||||
if (*it != '}') on_error("missing '}' in format string");
|
|
||||||
repl.arg_id = part_.part_kind == part::kind::arg_index
|
|
||||||
? arg_ref<Char>(part_.val.arg_index)
|
|
||||||
: arg_ref<Char>(part_.val.str);
|
|
||||||
auto part = part::make_replacement(repl);
|
|
||||||
part.arg_id_end = begin;
|
|
||||||
handler_(part);
|
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend counting_iterator operator+(counting_iterator it, difference_type n) {
|
||||||
|
it.count_ += static_cast<size_t>(n);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator*() const { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compiles a format string and invokes handler(part) for each parsed part.
|
template <typename Char, typename InputIt>
|
||||||
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||||
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
counting_iterator it) {
|
||||||
PartHandler handler) {
|
return it + (end - begin);
|
||||||
parse_format_string<IS_CONSTEXPR>(
|
|
||||||
format_str,
|
|
||||||
format_string_compiler<Char, PartHandler>(format_str, handler));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Range, typename Context, typename Id>
|
template <typename OutputIt> class truncating_iterator_base {
|
||||||
void format_arg(
|
protected:
|
||||||
basic_format_parse_context<typename Range::value_type>& parse_ctx,
|
OutputIt out_;
|
||||||
Context& ctx, Id arg_id) {
|
size_t limit_;
|
||||||
ctx.advance_to(
|
size_t count_ = 0;
|
||||||
visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// vformat_to is defined in a subnamespace to prevent ADL.
|
truncating_iterator_base() : out_(), limit_(0) {}
|
||||||
namespace cf {
|
|
||||||
template <typename Context, typename Range, typename CompiledFormat>
|
|
||||||
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
|
|
||||||
-> typename Context::iterator {
|
|
||||||
using char_type = typename Context::char_type;
|
|
||||||
basic_format_parse_context<char_type> parse_ctx(
|
|
||||||
to_string_view(cf.format_str_));
|
|
||||||
Context ctx(out.begin(), args);
|
|
||||||
|
|
||||||
const auto& parts = cf.parts();
|
truncating_iterator_base(OutputIt out, size_t limit)
|
||||||
for (auto part_it = std::begin(parts); part_it != std::end(parts);
|
: out_(out), limit_(limit) {}
|
||||||
++part_it) {
|
|
||||||
const auto& part = *part_it;
|
|
||||||
const auto& value = part.val;
|
|
||||||
|
|
||||||
using format_part_t = format_part<char_type>;
|
public:
|
||||||
switch (part.part_kind) {
|
using iterator_category = std::output_iterator_tag;
|
||||||
case format_part_t::kind::text: {
|
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
||||||
const auto text = value.str;
|
using difference_type = std::ptrdiff_t;
|
||||||
auto output = ctx.out();
|
using pointer = void;
|
||||||
auto&& it = reserve(output, text.size());
|
using reference = void;
|
||||||
it = std::copy_n(text.begin(), text.size(), it);
|
using _Unchecked_type =
|
||||||
ctx.advance_to(output);
|
truncating_iterator_base; // Mark iterator as checked.
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case format_part_t::kind::arg_index:
|
OutputIt base() const { return out_; }
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
size_t count() const { return count_; }
|
||||||
internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case format_part_t::kind::arg_name:
|
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
|
||||||
internal::format_arg<Range>(parse_ctx, ctx, value.str);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case format_part_t::kind::replacement: {
|
|
||||||
const auto& arg_id_value = value.repl.arg_id.val;
|
|
||||||
const auto arg = value.repl.arg_id.kind == arg_id_kind::index
|
|
||||||
? ctx.arg(arg_id_value.index)
|
|
||||||
: ctx.arg(arg_id_value.name);
|
|
||||||
|
|
||||||
auto specs = value.repl.specs;
|
|
||||||
|
|
||||||
handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
|
|
||||||
handle_dynamic_spec<precision_checker>(specs.precision,
|
|
||||||
specs.precision_ref, ctx);
|
|
||||||
|
|
||||||
error_handler h;
|
|
||||||
numeric_specs_checker<error_handler> checker(h, arg.type());
|
|
||||||
if (specs.align == align::numeric) checker.require_numeric_argument();
|
|
||||||
if (specs.sign != sign::none) checker.check_sign();
|
|
||||||
if (specs.alt) checker.require_numeric_argument();
|
|
||||||
if (specs.precision >= 0) checker.check_precision();
|
|
||||||
|
|
||||||
advance_to(parse_ctx, part.arg_id_end);
|
|
||||||
ctx.advance_to(
|
|
||||||
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ctx.out();
|
|
||||||
}
|
|
||||||
} // namespace cf
|
|
||||||
|
|
||||||
struct basic_compiled_format {};
|
|
||||||
|
|
||||||
template <typename S, typename = void>
|
|
||||||
struct compiled_format_base : basic_compiled_format {
|
|
||||||
using char_type = char_t<S>;
|
|
||||||
using parts_container = std::vector<internal::format_part<char_type>>;
|
|
||||||
|
|
||||||
parts_container compiled_parts;
|
|
||||||
|
|
||||||
explicit compiled_format_base(basic_string_view<char_type> format_str) {
|
|
||||||
compile_format_string<false>(format_str,
|
|
||||||
[this](const format_part<char_type>& part) {
|
|
||||||
compiled_parts.push_back(part);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts_container& parts() const { return compiled_parts; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, unsigned N> struct format_part_array {
|
// An output iterator that truncates the output and counts the number of objects
|
||||||
format_part<Char> data[N] = {};
|
// written to it.
|
||||||
FMT_CONSTEXPR format_part_array() = default;
|
template <typename OutputIt,
|
||||||
|
typename Enable = typename std::is_void<
|
||||||
|
typename std::iterator_traits<OutputIt>::value_type>::type>
|
||||||
|
class truncating_iterator;
|
||||||
|
|
||||||
|
template <typename OutputIt>
|
||||||
|
class truncating_iterator<OutputIt, std::false_type>
|
||||||
|
: public truncating_iterator_base<OutputIt> {
|
||||||
|
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
||||||
|
|
||||||
|
truncating_iterator() = default;
|
||||||
|
|
||||||
|
truncating_iterator(OutputIt out, size_t limit)
|
||||||
|
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||||
|
|
||||||
|
truncating_iterator& operator++() {
|
||||||
|
if (this->count_++ < this->limit_) ++this->out_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncating_iterator operator++(int) {
|
||||||
|
auto it = *this;
|
||||||
|
++*this;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type& operator*() const {
|
||||||
|
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, unsigned N>
|
template <typename OutputIt>
|
||||||
FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
|
class truncating_iterator<OutputIt, std::true_type>
|
||||||
basic_string_view<Char> format_str) {
|
: public truncating_iterator_base<OutputIt> {
|
||||||
format_part_array<Char, N> parts;
|
public:
|
||||||
unsigned counter = 0;
|
truncating_iterator() = default;
|
||||||
// This is not a lambda for compatibility with older compilers.
|
|
||||||
struct {
|
truncating_iterator(OutputIt out, size_t limit)
|
||||||
format_part<Char>* parts;
|
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||||
unsigned* counter;
|
|
||||||
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
template <typename T> truncating_iterator& operator=(T val) {
|
||||||
parts[(*counter)++] = part;
|
if (this->count_++ < this->limit_) *this->out_++ = val;
|
||||||
}
|
return *this;
|
||||||
} collector{parts.data, &counter};
|
|
||||||
compile_format_string<true>(format_str, collector);
|
|
||||||
if (counter < N) {
|
|
||||||
parts.data[counter] =
|
|
||||||
format_part<Char>::make_text(basic_string_view<Char>());
|
|
||||||
}
|
|
||||||
return parts;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
|
truncating_iterator& operator++() { return *this; }
|
||||||
return (a < b) ? b : a;
|
truncating_iterator& operator++(int) { return *this; }
|
||||||
}
|
truncating_iterator& operator*() { return *this; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A compile-time string which is compiled into fast formatting code.
|
||||||
|
class compiled_string {};
|
||||||
|
|
||||||
template <typename S>
|
template <typename S>
|
||||||
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
|
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||||
: basic_compiled_format {
|
|
||||||
using char_type = char_t<S>;
|
|
||||||
|
|
||||||
FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
|
/**
|
||||||
|
\rst
|
||||||
|
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
|
||||||
|
``constexpr if`` compiler support.
|
||||||
|
|
||||||
// Workaround for old compilers. Format string compilation will not be
|
**Example**::
|
||||||
// performed there anyway.
|
|
||||||
#if FMT_USE_CONSTEXPR
|
// Converts 42 into std::string using the most efficient method and no
|
||||||
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
|
// runtime format string processing.
|
||||||
constexpr_max(count_parts(to_string_view(S())), 1u);
|
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
#ifdef __cpp_if_constexpr
|
||||||
|
# define FMT_COMPILE(s) \
|
||||||
|
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
|
||||||
#else
|
#else
|
||||||
static const unsigned num_format_parts = 1;
|
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using parts_container = format_part<char_type>[num_format_parts];
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
template <typename Char, size_t N,
|
||||||
const parts_container& parts() const {
|
fmt::detail_exported::fixed_string<Char, N> Str>
|
||||||
static FMT_CONSTEXPR_DECL const auto compiled_parts =
|
struct udl_compiled_string : compiled_string {
|
||||||
compile_to_parts<char_type, num_format_parts>(
|
using char_type = Char;
|
||||||
internal::to_string_view(S()));
|
constexpr operator basic_string_view<char_type>() const {
|
||||||
return compiled_parts.data;
|
return {Str.data, N - 1};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename S, typename... Args>
|
template <typename T, typename... Tail>
|
||||||
class compiled_format : private compiled_format_base<S> {
|
const T& first(const T& value, const Tail&...) {
|
||||||
public:
|
return value;
|
||||||
using typename compiled_format_base<S>::char_type;
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
basic_string_view<char_type> format_str_;
|
|
||||||
|
|
||||||
template <typename Context, typename Range, typename CompiledFormat>
|
|
||||||
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
|
|
||||||
basic_format_args<Context> args) ->
|
|
||||||
typename Context::iterator;
|
|
||||||
|
|
||||||
public:
|
|
||||||
compiled_format() = delete;
|
|
||||||
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
|
|
||||||
: compiled_format_base<S>(format_str), format_str_(format_str) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __cpp_if_constexpr
|
#ifdef __cpp_if_constexpr
|
||||||
template <typename... Args> struct type_list {};
|
template <typename... Args> struct type_list {};
|
||||||
|
|
||||||
// Returns a reference to the argument at index N from [first, rest...].
|
// Returns a reference to the argument at index N from [first, rest...].
|
||||||
template <int N, typename T, typename... Args>
|
template <int N, typename T, typename... Args>
|
||||||
constexpr const auto& get(const T& first, const Args&... rest) {
|
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||||
|
[[maybe_unused]] const Args&... rest) {
|
||||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||||
if constexpr (N == 0)
|
if constexpr (N == 0)
|
||||||
return first;
|
return first;
|
||||||
@@ -342,6 +193,12 @@ constexpr const auto& get(const T& first, const Args&... rest) {
|
|||||||
return get<N - 1>(rest...);
|
return get<N - 1>(rest...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename... Args>
|
||||||
|
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
||||||
|
type_list<Args...>) {
|
||||||
|
return get_arg_index_by_name<Args...>(name);
|
||||||
|
}
|
||||||
|
|
||||||
template <int N, typename> struct get_type_impl;
|
template <int N, typename> struct get_type_impl;
|
||||||
|
|
||||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||||
@@ -358,9 +215,8 @@ template <typename Char> struct text {
|
|||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&...) const {
|
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||||
// TODO: reserve
|
return write<Char>(out, data);
|
||||||
return copy_str<Char>(data.begin(), data.end(), out);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -373,55 +229,100 @@ constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
|||||||
return {{&s[pos], size}};
|
return {{&s[pos], size}};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt, typename T,
|
template <typename Char> struct code_unit {
|
||||||
std::enable_if_t<std::is_integral_v<T>, int> = 0>
|
Char value;
|
||||||
OutputIt format_default(OutputIt out, T value) {
|
using char_type = Char;
|
||||||
// TODO: reserve
|
|
||||||
format_int fi(value);
|
template <typename OutputIt, typename... Args>
|
||||||
return std::copy(fi.data(), fi.data() + fi.size(), out);
|
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||||
|
return write<Char>(out, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This ensures that the argument type is convertible to `const T&`.
|
||||||
|
template <typename T, int N, typename... Args>
|
||||||
|
constexpr const T& get_arg_checked(const Args&... args) {
|
||||||
|
const auto& arg = get<N>(args...);
|
||||||
|
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||||
|
return arg.value;
|
||||||
|
} else {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
template <typename Char>
|
||||||
OutputIt format_default(OutputIt out, double value) {
|
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||||
writer w(out);
|
|
||||||
w.write(value);
|
|
||||||
return w.out();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
|
||||||
OutputIt format_default(OutputIt out, Char value) {
|
|
||||||
*out++ = value;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char, typename OutputIt>
|
|
||||||
OutputIt format_default(OutputIt out, const Char* value) {
|
|
||||||
auto length = std::char_traits<Char>::length(value);
|
|
||||||
return copy_str<Char>(value, value + length, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A replacement field that refers to argument N.
|
// A replacement field that refers to argument N.
|
||||||
template <typename Char, typename T, int N> struct field {
|
template <typename Char, typename T, int N> struct field {
|
||||||
using char_type = Char;
|
using char_type = Char;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
// This ensures that the argument type is convertile to `const T&`.
|
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||||
const T& arg = get<N>(args...);
|
|
||||||
return format_default<Char>(out, arg);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename T, int N>
|
template <typename Char, typename T, int N>
|
||||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||||
|
|
||||||
|
// A replacement field that refers to argument with name.
|
||||||
|
template <typename Char> struct runtime_named_field {
|
||||||
|
using char_type = Char;
|
||||||
|
basic_string_view<Char> name;
|
||||||
|
|
||||||
|
template <typename OutputIt, typename T>
|
||||||
|
constexpr static bool try_format_argument(
|
||||||
|
OutputIt& out,
|
||||||
|
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||||
|
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
||||||
|
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||||
|
if (arg_name == arg.name) {
|
||||||
|
out = write<Char>(out, arg.value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename... Args>
|
||||||
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
|
bool found = (try_format_argument(out, name, args) || ...);
|
||||||
|
if (!found) {
|
||||||
|
throw format_error("argument with specified name is not found");
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||||
|
|
||||||
|
// A replacement field that refers to argument N and has format specifiers.
|
||||||
|
template <typename Char, typename T, int N> struct spec_field {
|
||||||
|
using char_type = Char;
|
||||||
|
formatter<T, Char> fmt;
|
||||||
|
|
||||||
|
template <typename OutputIt, typename... Args>
|
||||||
|
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
||||||
|
const Args&... args) const {
|
||||||
|
const auto& vargs =
|
||||||
|
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||||
|
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||||
|
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char, typename T, int N>
|
||||||
|
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||||
|
|
||||||
template <typename L, typename R> struct concat {
|
template <typename L, typename R> struct concat {
|
||||||
L lhs;
|
L lhs;
|
||||||
R rhs;
|
R rhs;
|
||||||
using char_type = typename L::char_type;
|
using char_type = typename L::char_type;
|
||||||
|
|
||||||
template <typename OutputIt, typename... Args>
|
template <typename OutputIt, typename... Args>
|
||||||
OutputIt format(OutputIt out, const Args&... args) const {
|
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||||
out = lhs.format(out, args...);
|
out = lhs.format(out, args...);
|
||||||
return rhs.format(out, args...);
|
return rhs.format(out, args...);
|
||||||
}
|
}
|
||||||
@@ -450,7 +351,8 @@ constexpr auto compile_format_string(S format_str);
|
|||||||
|
|
||||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||||
constexpr auto parse_tail(T head, S format_str) {
|
constexpr auto parse_tail(T head, S format_str) {
|
||||||
if constexpr (POS != to_string_view(format_str).size()) {
|
if constexpr (POS !=
|
||||||
|
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||||
unknown_format>())
|
unknown_format>())
|
||||||
@@ -462,134 +364,276 @@ constexpr auto parse_tail(T head, S format_str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Char> struct parse_specs_result {
|
||||||
|
formatter<T, Char> fmt;
|
||||||
|
size_t end;
|
||||||
|
int next_arg_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int manual_indexing_id = -1;
|
||||||
|
|
||||||
|
template <typename T, typename Char>
|
||||||
|
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||||
|
size_t pos, int next_arg_id) {
|
||||||
|
str.remove_prefix(pos);
|
||||||
|
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
|
||||||
|
auto f = formatter<T, Char>();
|
||||||
|
auto end = f.parse(ctx);
|
||||||
|
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
||||||
|
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char> struct arg_id_handler {
|
||||||
|
arg_ref<Char> arg_id;
|
||||||
|
|
||||||
|
constexpr int operator()() {
|
||||||
|
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
constexpr int operator()(int id) {
|
||||||
|
arg_id = arg_ref<Char>(id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
constexpr int operator()(basic_string_view<Char> id) {
|
||||||
|
arg_id = arg_ref<Char>(id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void on_error(const char* message) { throw format_error(message); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Char> struct parse_arg_id_result {
|
||||||
|
arg_ref<Char> arg_id;
|
||||||
|
const Char* arg_id_end;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <int ID, typename Char>
|
||||||
|
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||||
|
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
||||||
|
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||||
|
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void> struct field_type {
|
||||||
|
using type = remove_cvref_t<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
|
||||||
|
using type = remove_cvref_t<decltype(T::value)>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
|
||||||
|
typename S>
|
||||||
|
constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||||
|
using char_type = typename S::char_type;
|
||||||
|
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||||
|
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
|
||||||
|
if constexpr (c == '}') {
|
||||||
|
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||||
|
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (c == ':') {
|
||||||
|
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||||
|
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||||
|
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||||
|
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||||
|
result.fmt},
|
||||||
|
format_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compiles a non-empty format string and returns the compiled representation
|
// Compiles a non-empty format string and returns the compiled representation
|
||||||
// or unknown_format() on unrecognized input.
|
// or unknown_format() on unrecognized input.
|
||||||
template <typename Args, size_t POS, int ID, typename S>
|
template <typename Args, size_t POS, int ID, typename S>
|
||||||
constexpr auto compile_format_string(S format_str) {
|
constexpr auto compile_format_string(S format_str) {
|
||||||
using char_type = typename S::char_type;
|
using char_type = typename S::char_type;
|
||||||
constexpr basic_string_view<char_type> str = format_str;
|
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||||
if constexpr (str[POS] == '{') {
|
if constexpr (str[POS] == '{') {
|
||||||
if (POS + 1 == str.size())
|
if constexpr (POS + 1 == str.size())
|
||||||
throw format_error("unmatched '{' in format string");
|
throw format_error("unmatched '{' in format string");
|
||||||
if constexpr (str[POS + 1] == '{') {
|
if constexpr (str[POS + 1] == '{') {
|
||||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||||
} else if constexpr (str[POS + 1] == '}') {
|
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||||
using type = get_type<ID, Args>;
|
static_assert(ID != manual_indexing_id,
|
||||||
if constexpr (std::is_same<type, int>::value) {
|
"cannot switch from manual to automatic argument indexing");
|
||||||
return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
|
constexpr auto next_id =
|
||||||
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
|
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
|
||||||
|
POS + 1, ID, next_id>(
|
||||||
format_str);
|
format_str);
|
||||||
} else {
|
} else {
|
||||||
return unknown_format();
|
constexpr auto arg_id_result =
|
||||||
}
|
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
|
||||||
|
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
|
||||||
|
constexpr char_type c =
|
||||||
|
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||||
|
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||||
|
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
||||||
|
static_assert(
|
||||||
|
ID == manual_indexing_id || ID == 0,
|
||||||
|
"cannot switch from automatic to manual argument indexing");
|
||||||
|
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
||||||
|
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||||
|
Args, arg_id_end_pos,
|
||||||
|
arg_index, manual_indexing_id>(
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||||
|
constexpr auto arg_index =
|
||||||
|
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||||
|
if constexpr (arg_index != invalid_arg_index) {
|
||||||
|
constexpr auto next_id =
|
||||||
|
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||||
|
return parse_replacement_field_then_tail<
|
||||||
|
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||||
|
arg_index, next_id>(format_str);
|
||||||
} else {
|
} else {
|
||||||
return unknown_format();
|
if constexpr (c == '}') {
|
||||||
|
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||||
|
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||||
|
format_str);
|
||||||
|
} else if constexpr (c == ':') {
|
||||||
|
return unknown_format(); // no type info for specs parsing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if constexpr (str[POS] == '}') {
|
} else if constexpr (str[POS] == '}') {
|
||||||
if (POS + 1 == str.size())
|
if constexpr (POS + 1 == str.size())
|
||||||
throw format_error("unmatched '}' in format string");
|
throw format_error("unmatched '}' in format string");
|
||||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||||
} else {
|
} else {
|
||||||
constexpr auto end = parse_text(str, POS + 1);
|
constexpr auto end = parse_text(str, POS + 1);
|
||||||
|
if constexpr (end - POS > 1) {
|
||||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||||
format_str);
|
format_str);
|
||||||
|
} else {
|
||||||
|
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
||||||
|
format_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // __cpp_if_constexpr
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
#if FMT_USE_CONSTEXPR
|
|
||||||
# ifdef __cpp_if_constexpr
|
|
||||||
template <typename... Args, typename S,
|
template <typename... Args, typename S,
|
||||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
constexpr auto compile(S format_str) {
|
constexpr auto compile(S format_str) {
|
||||||
constexpr basic_string_view<typename S::char_type> str = format_str;
|
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
|
||||||
if constexpr (str.size() == 0) {
|
if constexpr (str.size() == 0) {
|
||||||
return internal::make_text(str, 0, 0);
|
return detail::make_text(str, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
constexpr auto result =
|
constexpr auto result =
|
||||||
internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
|
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||||
format_str);
|
format_str);
|
||||||
if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
|
|
||||||
internal::unknown_format>()) {
|
|
||||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
|
||||||
} else {
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
#endif // __cpp_if_constexpr
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
#ifdef __cpp_if_constexpr
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args,
|
template <typename CompiledFormat, typename... Args,
|
||||||
typename Char = typename CompiledFormat::char_type,
|
typename Char = typename CompiledFormat::char_type,
|
||||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||||
basic_memory_buffer<Char> buffer;
|
const Args&... args) {
|
||||||
cf.format(std::back_inserter(buffer), args...);
|
auto s = std::basic_string<Char>();
|
||||||
return to_string(buffer);
|
cf.format(std::back_inserter(s), args...);
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||||
FMT_ENABLE_IF(internal::is_compiled_format<CompiledFormat>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||||
const Args&... args) {
|
const Args&... args) {
|
||||||
return cf.format(out, args...);
|
return cf.format(out, args...);
|
||||||
}
|
}
|
||||||
# else
|
|
||||||
template <typename... Args, typename S,
|
|
||||||
FMT_ENABLE_IF(is_compile_string<S>::value)>
|
|
||||||
constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
|
|
||||||
return internal::compiled_format<S, Args...>(to_string_view(format_str));
|
|
||||||
}
|
|
||||||
# endif // __cpp_if_constexpr
|
|
||||||
#endif // FMT_USE_CONSTEXPR
|
|
||||||
|
|
||||||
// Compiles the format string which must be a string literal.
|
template <typename S, typename... Args,
|
||||||
template <typename... Args, typename Char, size_t N>
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
auto compile(const Char (&format_str)[N])
|
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||||
-> internal::compiled_format<const Char*, Args...> {
|
Args&&... args) {
|
||||||
return internal::compiled_format<const Char*, Args...>(
|
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||||
basic_string_view<Char>(format_str, N - 1));
|
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||||
|
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||||
|
const auto& first = detail::first(args...);
|
||||||
|
if constexpr (detail::is_named_arg<
|
||||||
|
remove_cvref_t<decltype(first)>>::value) {
|
||||||
|
return fmt::to_string(first.value);
|
||||||
|
} else {
|
||||||
|
return fmt::to_string(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constexpr auto compiled = detail::compile<Args...>(S());
|
||||||
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
|
detail::unknown_format>()) {
|
||||||
|
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
} else {
|
||||||
|
return format(compiled, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
typename Char = typename CompiledFormat::char_type,
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||||
CompiledFormat>::value)>
|
constexpr auto compiled = detail::compile<Args...>(S());
|
||||||
std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
|
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||||
basic_memory_buffer<Char> buffer;
|
detail::unknown_format>()) {
|
||||||
using range = buffer_range<Char>;
|
return format_to(out,
|
||||||
using context = buffer_context<Char>;
|
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||||
internal::cf::vformat_to<context>(range(buffer), cf,
|
std::forward<Args>(args)...);
|
||||||
make_format_args<context>(args...));
|
} else {
|
||||||
return to_string(buffer);
|
return format_to(out, compiled, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
|
||||||
FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
|
|
||||||
CompiledFormat>::value)>
|
|
||||||
OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
|
||||||
const Args&... args) {
|
|
||||||
using char_type = typename CompiledFormat::char_type;
|
|
||||||
using range = internal::output_range<OutputIt, char_type>;
|
|
||||||
using context = format_context_t<OutputIt, char_type>;
|
|
||||||
return internal::cf::vformat_to<context>(range(out), cf,
|
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)>
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||||
const CompiledFormat& cf,
|
const S& format_str, Args&&... args) {
|
||||||
const Args&... args) {
|
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
|
||||||
auto it =
|
std::forward<Args>(args)...);
|
||||||
format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...);
|
|
||||||
return {it.base(), it.count()};
|
return {it.base(), it.count()};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename CompiledFormat, typename... Args>
|
template <typename S, typename... Args,
|
||||||
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
return format_to(internal::counting_iterator(), cf, args...).count();
|
size_t formatted_size(const S& format_str, const Args&... args) {
|
||||||
|
return format_to(detail::counting_iterator(), format_str, args...).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
|
void print(std::FILE* f, const S& format_str, const Args&... args) {
|
||||||
|
memory_buffer buffer;
|
||||||
|
format_to(std::back_inserter(buffer), format_str, args...);
|
||||||
|
detail::print(f, {buffer.data(), buffer.size()});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||||
|
void print(const S& format_str, const Args&... args) {
|
||||||
|
print(stdout, format_str, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
inline namespace literals {
|
||||||
|
template <detail_exported::fixed_string Str>
|
||||||
|
constexpr detail::udl_compiled_string<
|
||||||
|
remove_cvref_t<decltype(Str.data[0])>,
|
||||||
|
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
|
||||||
|
operator""_cf() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} // namespace literals
|
||||||
|
#endif
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_COMPILE_H_
|
#endif // FMT_COMPILE_H_
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,78 +1,2 @@
|
|||||||
// Formatting library for C++ - std::locale support
|
#include "xchar.h"
|
||||||
//
|
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
|
||||||
// Copyright (c) 2012 - present, Victor Zverovich
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// For the license information refer to format.h.
|
|
||||||
|
|
||||||
#ifndef FMT_LOCALE_H_
|
|
||||||
#define FMT_LOCALE_H_
|
|
||||||
|
|
||||||
#include <locale>
|
|
||||||
|
|
||||||
#include "format.h"
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
template <typename Char>
|
|
||||||
typename buffer_context<Char>::iterator vformat_to(
|
|
||||||
const std::locale& loc, buffer<Char>& buf,
|
|
||||||
basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
using range = buffer_range<Char>;
|
|
||||||
return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args,
|
|
||||||
internal::locale_ref(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
std::basic_string<Char> vformat(
|
|
||||||
const std::locale& loc, basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
basic_memory_buffer<Char> buffer;
|
|
||||||
internal::vformat_to(loc, buffer, format_str, args);
|
|
||||||
return fmt::to_string(buffer);
|
|
||||||
}
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
|
||||||
inline std::basic_string<Char> vformat(
|
|
||||||
const std::locale& loc, const S& format_str,
|
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
|
||||||
return internal::vformat(loc, to_string_view(format_str), args);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
|
||||||
inline std::basic_string<Char> format(const std::locale& loc,
|
|
||||||
const S& format_str, Args&&... args) {
|
|
||||||
return internal::vformat(
|
|
||||||
loc, to_string_view(format_str),
|
|
||||||
internal::make_args_checked<Args...>(format_str, args...));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename S, typename OutputIt, typename... Args,
|
|
||||||
typename Char = enable_if_t<
|
|
||||||
internal::is_output_iterator<OutputIt>::value, char_t<S>>>
|
|
||||||
inline OutputIt vformat_to(
|
|
||||||
OutputIt out, const std::locale& loc, const S& format_str,
|
|
||||||
format_args_t<type_identity_t<OutputIt>, Char> args) {
|
|
||||||
using range = internal::output_range<OutputIt, Char>;
|
|
||||||
return vformat_to<arg_formatter<range>>(
|
|
||||||
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename S, typename... Args,
|
|
||||||
FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&&
|
|
||||||
internal::is_string<S>::value)>
|
|
||||||
inline OutputIt format_to(OutputIt out, const std::locale& loc,
|
|
||||||
const S& format_str, Args&&... args) {
|
|
||||||
internal::check_format_string<Args...>(format_str);
|
|
||||||
using context = format_context_t<OutputIt, char_t<S>>;
|
|
||||||
format_arg_store<context, Args...> as{args...};
|
|
||||||
return vformat_to(out, loc, to_string_view(format_str),
|
|
||||||
basic_format_args<context>(as));
|
|
||||||
}
|
|
||||||
|
|
||||||
FMT_END_NAMESPACE
|
|
||||||
|
|
||||||
#endif // FMT_LOCALE_H_
|
|
||||||
|
|||||||
515
include/spdlog/fmt/bundled/os.h
Normal file
515
include/spdlog/fmt/bundled/os.h
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
// Formatting library for C++ - optional OS-specific functionality
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_OS_H_
|
||||||
|
#define FMT_OS_H_
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <clocale> // locale_t
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib> // strtod_l
|
||||||
|
#include <system_error> // std::system_error
|
||||||
|
|
||||||
|
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||||
|
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
// UWP doesn't provide _pipe.
|
||||||
|
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||||
|
# include <winapifamily.h>
|
||||||
|
#endif
|
||||||
|
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||||
|
defined(__linux__)) && \
|
||||||
|
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||||
|
# include <fcntl.h> // for O_RDONLY
|
||||||
|
# define FMT_USE_FCNTL 1
|
||||||
|
#else
|
||||||
|
# define FMT_USE_FCNTL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FMT_POSIX
|
||||||
|
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||||
|
// Fix warnings about deprecated symbols.
|
||||||
|
# define FMT_POSIX(call) _##call
|
||||||
|
# else
|
||||||
|
# define FMT_POSIX(call) call
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||||
|
#ifdef FMT_SYSTEM
|
||||||
|
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||||
|
#else
|
||||||
|
# define FMT_SYSTEM(call) ::call
|
||||||
|
# ifdef _WIN32
|
||||||
|
// Fix warnings about deprecated symbols.
|
||||||
|
# define FMT_POSIX_CALL(call) ::_##call
|
||||||
|
# else
|
||||||
|
# define FMT_POSIX_CALL(call) ::call
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Retries the expression while it evaluates to error_result and errno
|
||||||
|
// equals to EINTR.
|
||||||
|
#ifndef _WIN32
|
||||||
|
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||||
|
do { \
|
||||||
|
(result) = (expression); \
|
||||||
|
} while ((result) == (error_result) && errno == EINTR)
|
||||||
|
#else
|
||||||
|
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
A reference to a null-terminated string. It can be constructed from a C
|
||||||
|
string or ``std::string``.
|
||||||
|
|
||||||
|
You can use one of the following type aliases for common character types:
|
||||||
|
|
||||||
|
+---------------+-----------------------------+
|
||||||
|
| Type | Definition |
|
||||||
|
+===============+=============================+
|
||||||
|
| cstring_view | basic_cstring_view<char> |
|
||||||
|
+---------------+-----------------------------+
|
||||||
|
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||||
|
+---------------+-----------------------------+
|
||||||
|
|
||||||
|
This class is most useful as a parameter type to allow passing
|
||||||
|
different types of strings to a function, for example::
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
std::string format(cstring_view format_str, const Args & ... args);
|
||||||
|
|
||||||
|
format("{}", 42);
|
||||||
|
format(std::string("{}"), 42);
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename Char> class basic_cstring_view {
|
||||||
|
private:
|
||||||
|
const Char* data_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructs a string reference object from a C string. */
|
||||||
|
basic_cstring_view(const Char* s) : data_(s) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Constructs a string reference from an ``std::string`` object.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||||
|
|
||||||
|
/** Returns the pointer to a C string. */
|
||||||
|
const Char* c_str() const { return data_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
using cstring_view = basic_cstring_view<char>;
|
||||||
|
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||||
|
|
||||||
|
template <typename Char> struct formatter<std::error_code, Char> {
|
||||||
|
template <typename ParseContext>
|
||||||
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
|
return ctx.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FormatContext>
|
||||||
|
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||||
|
-> decltype(ctx.out()) {
|
||||||
|
auto out = ctx.out();
|
||||||
|
out = detail::write_bytes(out, ec.category().name(),
|
||||||
|
basic_format_specs<Char>());
|
||||||
|
out = detail::write<Char>(out, Char(':'));
|
||||||
|
out = detail::write<Char>(out, ec.value());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
// A converter from UTF-16 to UTF-8.
|
||||||
|
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||||
|
class utf16_to_utf8 {
|
||||||
|
private:
|
||||||
|
memory_buffer buffer_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
utf16_to_utf8() {}
|
||||||
|
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||||
|
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||||
|
size_t size() const { return buffer_.size() - 1; }
|
||||||
|
const char* c_str() const { return &buffer_[0]; }
|
||||||
|
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||||
|
|
||||||
|
// Performs conversion returning a system error code instead of
|
||||||
|
// throwing exception on conversion error. This method may still throw
|
||||||
|
// in case of memory allocation error.
|
||||||
|
FMT_API int convert(basic_string_view<wchar_t> s);
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||||
|
const char* message) FMT_NOEXCEPT;
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||||
|
format_args args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Constructs a :class:`std::system_error` object with the description
|
||||||
|
of the form
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
*<message>*: *<system-message>*
|
||||||
|
|
||||||
|
where *<message>* is the formatted message and *<system-message>* is the
|
||||||
|
system message corresponding to the error code.
|
||||||
|
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||||
|
If *error_code* is not a valid error code such as -1, the system message
|
||||||
|
will look like "error -1".
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
// This throws a system_error with the description
|
||||||
|
// cannot open file 'madeup': The system cannot find the file specified.
|
||||||
|
// or similar (system message may vary).
|
||||||
|
const char *filename = "madeup";
|
||||||
|
LPOFSTRUCT of = LPOFSTRUCT();
|
||||||
|
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||||
|
if (file == HFILE_ERROR) {
|
||||||
|
throw fmt::windows_error(GetLastError(),
|
||||||
|
"cannot open file '{}'", filename);
|
||||||
|
}
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename... Args>
|
||||||
|
std::system_error windows_error(int error_code, string_view message,
|
||||||
|
const Args&... args) {
|
||||||
|
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reports a Windows error without throwing an exception.
|
||||||
|
// Can be used to report errors from destructors.
|
||||||
|
FMT_API void report_windows_error(int error_code,
|
||||||
|
const char* message) FMT_NOEXCEPT;
|
||||||
|
#else
|
||||||
|
inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||||
|
return std::system_category();
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
// std::system is not available on some platforms such as iOS (#2248).
|
||||||
|
#ifdef __OSX__
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||||
|
void say(const S& format_str, Args&&... args) {
|
||||||
|
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// A buffered file.
|
||||||
|
class buffered_file {
|
||||||
|
private:
|
||||||
|
FILE* file_;
|
||||||
|
|
||||||
|
friend class file;
|
||||||
|
|
||||||
|
explicit buffered_file(FILE* f) : file_(f) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
buffered_file(const buffered_file&) = delete;
|
||||||
|
void operator=(const buffered_file&) = delete;
|
||||||
|
|
||||||
|
// Constructs a buffered_file object which doesn't represent any file.
|
||||||
|
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||||
|
|
||||||
|
// Destroys the object closing the file it represents if any.
|
||||||
|
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||||
|
other.file_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffered_file& operator=(buffered_file&& other) {
|
||||||
|
close();
|
||||||
|
file_ = other.file_;
|
||||||
|
other.file_ = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opens a file.
|
||||||
|
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||||
|
|
||||||
|
// Closes the file.
|
||||||
|
FMT_API void close();
|
||||||
|
|
||||||
|
// Returns the pointer to a FILE object representing this file.
|
||||||
|
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||||
|
|
||||||
|
// We place parentheses around fileno to workaround a bug in some versions
|
||||||
|
// of MinGW that define fileno as a macro.
|
||||||
|
FMT_API int(fileno)() const;
|
||||||
|
|
||||||
|
void vprint(string_view format_str, format_args args) {
|
||||||
|
fmt::vprint(file_, format_str, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
inline void print(string_view format_str, const Args&... args) {
|
||||||
|
vprint(format_str, fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#if FMT_USE_FCNTL
|
||||||
|
// A file. Closed file is represented by a file object with descriptor -1.
|
||||||
|
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||||
|
// fmt::system_error in case of failure. Note that some errors such as
|
||||||
|
// closing the file multiple times will cause a crash on Windows rather
|
||||||
|
// than an exception. You can get standard behavior by overriding the
|
||||||
|
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||||
|
class file {
|
||||||
|
private:
|
||||||
|
int fd_; // File descriptor.
|
||||||
|
|
||||||
|
// Constructs a file object with a given descriptor.
|
||||||
|
explicit file(int fd) : fd_(fd) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Possible values for the oflag argument to the constructor.
|
||||||
|
enum {
|
||||||
|
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||||
|
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||||
|
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||||
|
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||||
|
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
|
||||||
|
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constructs a file object which doesn't represent any file.
|
||||||
|
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||||
|
|
||||||
|
// Opens a file and constructs a file object representing this file.
|
||||||
|
FMT_API file(cstring_view path, int oflag);
|
||||||
|
|
||||||
|
public:
|
||||||
|
file(const file&) = delete;
|
||||||
|
void operator=(const file&) = delete;
|
||||||
|
|
||||||
|
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||||
|
|
||||||
|
// Move assignment is not noexcept because close may throw.
|
||||||
|
file& operator=(file&& other) {
|
||||||
|
close();
|
||||||
|
fd_ = other.fd_;
|
||||||
|
other.fd_ = -1;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroys the object closing the file it represents if any.
|
||||||
|
FMT_API ~file() FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
// Returns the file descriptor.
|
||||||
|
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||||
|
|
||||||
|
// Closes the file.
|
||||||
|
FMT_API void close();
|
||||||
|
|
||||||
|
// Returns the file size. The size has signed type for consistency with
|
||||||
|
// stat::st_size.
|
||||||
|
FMT_API long long size() const;
|
||||||
|
|
||||||
|
// Attempts to read count bytes from the file into the specified buffer.
|
||||||
|
FMT_API size_t read(void* buffer, size_t count);
|
||||||
|
|
||||||
|
// Attempts to write count bytes from the specified buffer to the file.
|
||||||
|
FMT_API size_t write(const void* buffer, size_t count);
|
||||||
|
|
||||||
|
// Duplicates a file descriptor with the dup function and returns
|
||||||
|
// the duplicate as a file object.
|
||||||
|
FMT_API static file dup(int fd);
|
||||||
|
|
||||||
|
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||||
|
// necessary.
|
||||||
|
FMT_API void dup2(int fd);
|
||||||
|
|
||||||
|
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||||
|
// necessary.
|
||||||
|
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
|
||||||
|
|
||||||
|
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||||
|
// and writing respectively.
|
||||||
|
FMT_API static void pipe(file& read_end, file& write_end);
|
||||||
|
|
||||||
|
// Creates a buffered_file object associated with this file and detaches
|
||||||
|
// this file object from the file.
|
||||||
|
FMT_API buffered_file fdopen(const char* mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns the memory page size.
|
||||||
|
long getpagesize();
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
struct buffer_size {
|
||||||
|
buffer_size() = default;
|
||||||
|
size_t value = 0;
|
||||||
|
buffer_size operator=(size_t val) const {
|
||||||
|
auto bs = buffer_size();
|
||||||
|
bs.value = val;
|
||||||
|
return bs;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ostream_params {
|
||||||
|
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||||
|
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||||
|
|
||||||
|
ostream_params() {}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||||
|
oflag = new_oflag;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
ostream_params(T... params, detail::buffer_size bs)
|
||||||
|
: ostream_params(params...) {
|
||||||
|
this->buffer_size = bs.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
|
constexpr detail::buffer_size buffer_size;
|
||||||
|
|
||||||
|
/** A fast output stream which is not thread-safe. */
|
||||||
|
class FMT_API ostream final : private detail::buffer<char> {
|
||||||
|
private:
|
||||||
|
file file_;
|
||||||
|
|
||||||
|
void flush() {
|
||||||
|
if (size() == 0) return;
|
||||||
|
file_.write(data(), size());
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void grow(size_t) override;
|
||||||
|
|
||||||
|
ostream(cstring_view path, const detail::ostream_params& params)
|
||||||
|
: file_(path, params.oflag) {
|
||||||
|
set(new char[params.buffer_size], params.buffer_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
ostream(ostream&& other)
|
||||||
|
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||||
|
file_(std::move(other.file_)) {
|
||||||
|
other.clear();
|
||||||
|
other.set(nullptr, 0);
|
||||||
|
}
|
||||||
|
~ostream() {
|
||||||
|
flush();
|
||||||
|
delete[] data();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
friend ostream output_file(cstring_view path, T... params);
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
flush();
|
||||||
|
file_.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||||
|
output to the file.
|
||||||
|
*/
|
||||||
|
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||||
|
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||||
|
fmt::make_format_args(args...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Opens a file for writing. Supported parameters passed in *params*:
|
||||||
|
|
||||||
|
* ``<integer>``: Flags passed to `open
|
||||||
|
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
|
||||||
|
(``file::WRONLY | file::CREATE`` by default)
|
||||||
|
* ``buffer_size=<integer>``: Output buffer size
|
||||||
|
|
||||||
|
**Example**::
|
||||||
|
|
||||||
|
auto out = fmt::output_file("guide.txt");
|
||||||
|
out.print("Don't {}", "Panic");
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
template <typename... T>
|
||||||
|
inline ostream output_file(cstring_view path, T... params) {
|
||||||
|
return {path, detail::ostream_params(params...)};
|
||||||
|
}
|
||||||
|
#endif // FMT_USE_FCNTL
|
||||||
|
|
||||||
|
#ifdef FMT_LOCALE
|
||||||
|
// A "C" numeric locale.
|
||||||
|
class locale {
|
||||||
|
private:
|
||||||
|
# ifdef _WIN32
|
||||||
|
using locale_t = _locale_t;
|
||||||
|
|
||||||
|
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||||
|
|
||||||
|
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||||
|
return _strtod_l(nptr, endptr, loc);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
locale_t locale_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using type = locale_t;
|
||||||
|
locale(const locale&) = delete;
|
||||||
|
void operator=(const locale&) = delete;
|
||||||
|
|
||||||
|
locale() {
|
||||||
|
# ifndef _WIN32
|
||||||
|
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||||
|
# else
|
||||||
|
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||||
|
# endif
|
||||||
|
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||||
|
}
|
||||||
|
~locale() { freelocale(locale_); }
|
||||||
|
|
||||||
|
type get() const { return locale_; }
|
||||||
|
|
||||||
|
// Converts string to floating-point number and advances str past the end
|
||||||
|
// of the parsed input.
|
||||||
|
double strtod(const char*& str) const {
|
||||||
|
char* end = nullptr;
|
||||||
|
double result = strtod_l(str, &end, locale_);
|
||||||
|
str = end;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||||
|
#endif // FMT_LOCALE
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_OS_H_
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
template <typename CHar> class basic_printf_parse_context;
|
template <typename Char> class basic_printf_parse_context;
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||||
|
|
||||||
namespace internal {
|
namespace detail {
|
||||||
|
|
||||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||||
private:
|
private:
|
||||||
@@ -49,17 +49,27 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct converter {
|
||||||
|
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||||
|
};
|
||||||
|
|
||||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||||
private:
|
private:
|
||||||
// Hide all operator<< from std::basic_ostream<Char>.
|
void_t<> operator<<(converter);
|
||||||
void_t<> operator<<(null<>);
|
|
||||||
void_t<> operator<<(const Char*);
|
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
|
|
||||||
!std::is_enum<T>::value)>
|
|
||||||
void_t<> operator<<(T);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Hide insertion operators for built-in types.
|
||||||
|
template <typename Char, typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||||
|
template <typename Char, typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||||
|
template <typename Traits>
|
||||||
|
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||||
|
|
||||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||||
// std::ostream).
|
// std::ostream).
|
||||||
template <typename T, typename Char> class is_streamable {
|
template <typename T, typename Char> class is_streamable {
|
||||||
@@ -75,12 +85,14 @@ template <typename T, typename Char> class is_streamable {
|
|||||||
using result = decltype(test<T>(0));
|
using result = decltype(test<T>(0));
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
is_streamable() = default;
|
||||||
|
|
||||||
static const bool value = result::value;
|
static const bool value = result::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Write the content of buf to os.
|
// Write the content of buf to os.
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void write(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 = std::make_unsigned<std::streamsize>::type;
|
||||||
unsigned_streamsize size = buf.size();
|
unsigned_streamsize size = buf.size();
|
||||||
@@ -101,16 +113,17 @@ void format_value(buffer<Char>& buf, const T& value,
|
|||||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||||
if (loc) output.imbue(loc.get<std::locale>());
|
if (loc) output.imbue(loc.get<std::locale>());
|
||||||
#endif
|
#endif
|
||||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
|
||||||
output << value;
|
output << value;
|
||||||
buf.resize(buf.size());
|
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||||
|
buf.try_resize(buf.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||||
template <typename T, typename Char>
|
template <typename T, typename Char>
|
||||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||||
: private formatter<basic_string_view<Char>, Char> {
|
: private formatter<basic_string_view<Char>, Char> {
|
||||||
auto parse(basic_format_parse_context<Char>& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||||
|
-> decltype(ctx.begin()) {
|
||||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||||
}
|
}
|
||||||
template <typename ParseCtx,
|
template <typename ParseCtx,
|
||||||
@@ -136,14 +149,15 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
|||||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace internal
|
} // namespace detail
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
internal::vformat_to(buffer, format_str, args);
|
detail::vformat_to(buffer, format_str, args);
|
||||||
internal::write(os, buffer);
|
detail::write_buffer(os, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,11 +169,12 @@ void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
|||||||
fmt::print(cerr, "Don't {}!", "panic");
|
fmt::print(cerr, "Don't {}!", "panic");
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
|
FMT_MODULE_EXPORT
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... Args,
|
||||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||||
vprint(os, to_string_view(format_str),
|
vprint(os, to_string_view(format_str),
|
||||||
internal::make_args_checked<Args...>(format_str, args...));
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
}
|
}
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#include "os.h"
|
|
||||||
#warning "fmt/posix.h is deprecated; use fmt/os.h instead"
|
|
||||||
@@ -10,11 +10,54 @@
|
|||||||
|
|
||||||
#include <algorithm> // std::max
|
#include <algorithm> // std::max
|
||||||
#include <limits> // std::numeric_limits
|
#include <limits> // std::numeric_limits
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
#include "ostream.h"
|
#include "format.h"
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace internal {
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||||
|
|
||||||
|
template <typename Char>
|
||||||
|
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||||
|
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||||
|
private:
|
||||||
|
OutputIt out_;
|
||||||
|
basic_format_args<basic_printf_context> args_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using char_type = Char;
|
||||||
|
using format_arg = basic_format_arg<basic_printf_context>;
|
||||||
|
using parse_context_type = basic_printf_parse_context<Char>;
|
||||||
|
template <typename T> using formatter_type = printf_formatter<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
\rst
|
||||||
|
Constructs a ``printf_context`` object. References to the arguments are
|
||||||
|
stored in the context object so make sure they have appropriate lifetimes.
|
||||||
|
\endrst
|
||||||
|
*/
|
||||||
|
basic_printf_context(OutputIt out,
|
||||||
|
basic_format_args<basic_printf_context> args)
|
||||||
|
: out_(out), args_(args) {}
|
||||||
|
|
||||||
|
OutputIt out() { return out_; }
|
||||||
|
void advance_to(OutputIt it) { out_ = it; }
|
||||||
|
|
||||||
|
detail::locale_ref locale() { return {}; }
|
||||||
|
|
||||||
|
format_arg arg(int id) const { return args_.get(id); }
|
||||||
|
|
||||||
|
FMT_CONSTEXPR void on_error(const char* message) {
|
||||||
|
detail::error_handler().on_error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FMT_BEGIN_DETAIL_NAMESPACE
|
||||||
|
|
||||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||||
// signed and unsigned integers.
|
// signed and unsigned integers.
|
||||||
@@ -90,11 +133,11 @@ template <typename T, typename Context> class arg_converter {
|
|||||||
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) {
|
if (is_signed) {
|
||||||
arg_ = internal::make_arg<Context>(
|
arg_ = detail::make_arg<Context>(
|
||||||
static_cast<int>(static_cast<target_type>(value)));
|
static_cast<int>(static_cast<target_type>(value)));
|
||||||
} else {
|
} else {
|
||||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||||
arg_ = internal::make_arg<Context>(
|
arg_ = detail::make_arg<Context>(
|
||||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -102,9 +145,9 @@ template <typename T, typename Context> class arg_converter {
|
|||||||
// 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.
|
||||||
arg_ = internal::make_arg<Context>(static_cast<long long>(value));
|
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||||
} else {
|
} else {
|
||||||
arg_ = internal::make_arg<Context>(
|
arg_ = detail::make_arg<Context>(
|
||||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +176,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) {
|
||||||
arg_ = internal::make_arg<Context>(
|
arg_ = detail::make_arg<Context>(
|
||||||
static_cast<typename Context::char_type>(value));
|
static_cast<typename Context::char_type>(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +184,13 @@ template <typename Context> class char_converter {
|
|||||||
void operator()(T) {} // No conversion needed for non-integral types.
|
void operator()(T) {} // No conversion needed for non-integral types.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// An argument visitor that return a pointer to a C string if argument is a
|
||||||
|
// string or null otherwise.
|
||||||
|
template <typename Char> struct get_cstring {
|
||||||
|
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||||
|
const Char* operator()(const Char* s) { return s; }
|
||||||
|
};
|
||||||
|
|
||||||
// Checks if an argument is a valid printf width specifier and sets
|
// Checks if an argument is a valid printf width specifier and sets
|
||||||
// left alignment if it is negative.
|
// left alignment if it is negative.
|
||||||
template <typename Char> class printf_width_handler {
|
template <typename Char> class printf_width_handler {
|
||||||
@@ -155,7 +205,7 @@ template <typename Char> class printf_width_handler {
|
|||||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||||
unsigned operator()(T value) {
|
unsigned operator()(T value) {
|
||||||
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 (internal::is_negative(value)) {
|
if (detail::is_negative(value)) {
|
||||||
specs_.align = align::left;
|
specs_.align = align::left;
|
||||||
width = 0 - width;
|
width = 0 - width;
|
||||||
}
|
}
|
||||||
@@ -171,216 +221,85 @@ template <typename Char> class printf_width_handler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Context>
|
// The ``printf`` argument formatter.
|
||||||
void printf(buffer<Char>& buf, basic_string_view<Char> format,
|
template <typename OutputIt, typename Char>
|
||||||
basic_format_args<Context> args) {
|
class printf_arg_formatter : public arg_formatter<Char> {
|
||||||
Context(std::back_inserter(buf), format, args).format();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char, typename Context>
|
|
||||||
internal::truncating_iterator<OutputIt> printf(
|
|
||||||
internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format,
|
|
||||||
basic_format_args<Context> args) {
|
|
||||||
return Context(it, format, args).format();
|
|
||||||
}
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
using internal::printf; // For printing into memory_buffer.
|
|
||||||
|
|
||||||
template <typename Range> class printf_arg_formatter;
|
|
||||||
|
|
||||||
template <typename Char>
|
|
||||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
|
||||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
|
||||||
};
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
\rst
|
|
||||||
The ``printf`` argument formatter.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
template <typename Range>
|
|
||||||
class printf_arg_formatter : public internal::arg_formatter_base<Range> {
|
|
||||||
public:
|
|
||||||
using iterator = typename Range::iterator;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using char_type = typename Range::value_type;
|
using base = arg_formatter<Char>;
|
||||||
using base = internal::arg_formatter_base<Range>;
|
using context_type = basic_printf_context<OutputIt, Char>;
|
||||||
using context_type = basic_printf_context<iterator, char_type>;
|
using format_specs = basic_format_specs<Char>;
|
||||||
|
|
||||||
context_type& context_;
|
context_type& context_;
|
||||||
|
|
||||||
void write_null_pointer(char) {
|
OutputIt write_null_pointer(bool is_string = false) {
|
||||||
this->specs()->type = 0;
|
auto s = this->specs;
|
||||||
this->write("(nil)");
|
s.type = 0;
|
||||||
}
|
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||||
|
|
||||||
void write_null_pointer(wchar_t) {
|
|
||||||
this->specs()->type = 0;
|
|
||||||
this->write(L"(nil)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using format_specs = typename base::format_specs;
|
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||||
|
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||||
|
|
||||||
/**
|
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||||
\rst
|
|
||||||
Constructs an argument formatter object.
|
|
||||||
*buffer* is a reference to the output buffer and *specs* contains format
|
|
||||||
specifier information for standard argument types.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
|
|
||||||
: base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
|
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
|
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||||
iterator operator()(T value) {
|
OutputIt operator()(T value) {
|
||||||
// MSVC2013 fails to compile separate overloads for bool and char_type so
|
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||||
// use std::is_same instead.
|
// std::is_same instead.
|
||||||
if (std::is_same<T, bool>::value) {
|
if (std::is_same<T, Char>::value) {
|
||||||
format_specs& fmt_specs = *this->specs();
|
format_specs fmt_specs = this->specs;
|
||||||
if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0);
|
|
||||||
fmt_specs.type = 0;
|
|
||||||
this->write(value != 0);
|
|
||||||
} else if (std::is_same<T, char_type>::value) {
|
|
||||||
format_specs& fmt_specs = *this->specs();
|
|
||||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||||
return (*this)(static_cast<int>(value));
|
return (*this)(static_cast<int>(value));
|
||||||
fmt_specs.sign = sign::none;
|
fmt_specs.sign = sign::none;
|
||||||
fmt_specs.alt = false;
|
fmt_specs.alt = false;
|
||||||
|
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||||
|
// align::numeric needs to be overwritten here since the '0' flag is
|
||||||
|
// ignored for non-numeric types
|
||||||
|
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||||
fmt_specs.align = align::right;
|
fmt_specs.align = align::right;
|
||||||
return base::operator()(value);
|
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||||
} else {
|
|
||||||
return base::operator()(value);
|
|
||||||
}
|
}
|
||||||
return this->out();
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||||
iterator operator()(T value) {
|
OutputIt operator()(T value) {
|
||||||
return base::operator()(value);
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated C string. */
|
/** Formats a null-terminated C string. */
|
||||||
iterator operator()(const char* value) {
|
OutputIt operator()(const char* value) {
|
||||||
if (value)
|
if (value) return base::operator()(value);
|
||||||
base::operator()(value);
|
return write_null_pointer(this->specs.type != 'p');
|
||||||
else if (this->specs()->type == 'p')
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
else
|
|
||||||
this->write("(null)");
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats a null-terminated wide C string. */
|
/** Formats a null-terminated wide C string. */
|
||||||
iterator operator()(const wchar_t* value) {
|
OutputIt operator()(const wchar_t* value) {
|
||||||
if (value)
|
if (value) return base::operator()(value);
|
||||||
base::operator()(value);
|
return write_null_pointer(this->specs.type != 'p');
|
||||||
else if (this->specs()->type == 'p')
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
else
|
|
||||||
this->write(L"(null)");
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator()(basic_string_view<char_type> value) {
|
OutputIt operator()(basic_string_view<Char> value) {
|
||||||
return base::operator()(value);
|
return base::operator()(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator()(monostate value) { return base::operator()(value); }
|
|
||||||
|
|
||||||
/** Formats a pointer. */
|
/** Formats a pointer. */
|
||||||
iterator operator()(const void* value) {
|
OutputIt operator()(const void* value) {
|
||||||
if (value) return base::operator()(value);
|
return value ? base::operator()(value) : write_null_pointer();
|
||||||
this->specs()->type = 0;
|
|
||||||
write_null_pointer(char_type());
|
|
||||||
return this->out();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Formats an argument of a custom (user-defined) type. */
|
/** Formats an argument of a custom (user-defined) type. */
|
||||||
iterator operator()(typename basic_format_arg<context_type>::handle handle) {
|
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||||
handle.format(context_.parse_context(), context_);
|
auto parse_ctx =
|
||||||
return this->out();
|
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||||
|
handle.format(parse_ctx, context_);
|
||||||
|
return this->out;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T> struct printf_formatter {
|
template <typename Char>
|
||||||
printf_formatter() = delete;
|
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||||
|
|
||||||
template <typename ParseContext>
|
|
||||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
|
||||||
return ctx.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename FormatContext>
|
|
||||||
auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) {
|
|
||||||
internal::format_value(internal::get_container(ctx.out()), value);
|
|
||||||
return ctx.out();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** This template formats data and writes the output to a writer. */
|
|
||||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
|
||||||
public:
|
|
||||||
/** The character type for the output. */
|
|
||||||
using char_type = Char;
|
|
||||||
using iterator = OutputIt;
|
|
||||||
using format_arg = basic_format_arg<basic_printf_context>;
|
|
||||||
using parse_context_type = basic_printf_parse_context<Char>;
|
|
||||||
template <typename T> using formatter_type = printf_formatter<T>;
|
|
||||||
|
|
||||||
private:
|
|
||||||
using format_specs = basic_format_specs<char_type>;
|
|
||||||
|
|
||||||
OutputIt out_;
|
|
||||||
basic_format_args<basic_printf_context> args_;
|
|
||||||
parse_context_type parse_ctx_;
|
|
||||||
|
|
||||||
static void parse_flags(format_specs& specs, const Char*& it,
|
|
||||||
const Char* end);
|
|
||||||
|
|
||||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
|
||||||
// argument.
|
|
||||||
format_arg get_arg(int arg_index = -1);
|
|
||||||
|
|
||||||
// Parses argument index, flags and width and returns the argument index.
|
|
||||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
\rst
|
|
||||||
Constructs a ``printf_context`` object. References to the arguments and
|
|
||||||
the writer are stored in the context object so make sure they have
|
|
||||||
appropriate lifetimes.
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
|
||||||
basic_format_args<basic_printf_context> args)
|
|
||||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
|
||||||
|
|
||||||
OutputIt out() { return out_; }
|
|
||||||
void advance_to(OutputIt it) { out_ = it; }
|
|
||||||
|
|
||||||
internal::locale_ref locale() { return {}; }
|
|
||||||
|
|
||||||
format_arg arg(int id) const { return args_.get(id); }
|
|
||||||
|
|
||||||
parse_context_type& parse_context() { return parse_ctx_; }
|
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_error(const char* message) {
|
|
||||||
parse_ctx_.on_error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Formats stored arguments and writes the output to the range. */
|
|
||||||
template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
|
|
||||||
OutputIt format();
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
|
||||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
|
||||||
const Char*& it,
|
|
||||||
const Char* end) {
|
const Char* end) {
|
||||||
for (; it != end; ++it) {
|
for (; it != end; ++it) {
|
||||||
switch (*it) {
|
switch (*it) {
|
||||||
@@ -394,7 +313,9 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
|||||||
specs.fill[0] = '0';
|
specs.fill[0] = '0';
|
||||||
break;
|
break;
|
||||||
case ' ':
|
case ' ':
|
||||||
|
if (specs.sign != sign::plus) {
|
||||||
specs.sign = sign::space;
|
specs.sign = sign::space;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '#':
|
case '#':
|
||||||
specs.alt = true;
|
specs.alt = true;
|
||||||
@@ -405,35 +326,24 @@ void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename Char, typename GetArg>
|
||||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
int parse_header(const Char*& it, const Char* end,
|
||||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
basic_format_specs<Char>& specs, GetArg get_arg) {
|
||||||
if (arg_index < 0)
|
|
||||||
arg_index = parse_ctx_.next_arg_id();
|
|
||||||
else
|
|
||||||
parse_ctx_.check_arg_id(--arg_index);
|
|
||||||
return internal::get_arg(*this, arg_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
|
||||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
|
||||||
const Char* end,
|
|
||||||
format_specs& specs) {
|
|
||||||
int arg_index = -1;
|
int arg_index = -1;
|
||||||
char_type c = *it;
|
Char c = *it;
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
// Parse an argument index (if followed by '$') or a width possibly
|
// Parse an argument index (if followed by '$') or a width possibly
|
||||||
// preceded with '0' flag(s).
|
// preceded with '0' flag(s).
|
||||||
internal::error_handler eh;
|
int value = parse_nonnegative_int(it, end, -1);
|
||||||
int value = parse_nonnegative_int(it, end, eh);
|
|
||||||
if (it != end && *it == '$') { // value is an argument index
|
if (it != end && *it == '$') { // value is an argument index
|
||||||
++it;
|
++it;
|
||||||
arg_index = value;
|
arg_index = value != -1 ? value : max_value<int>();
|
||||||
} else {
|
} else {
|
||||||
if (c == '0') specs.fill[0] = '0';
|
if (c == '0') specs.fill[0] = '0';
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
// Nonzero value means that we parsed width and don't need to
|
// Nonzero value means that we parsed width and don't need to
|
||||||
// parse it or flags again, so return now.
|
// parse it or flags again, so return now.
|
||||||
|
if (value == -1) FMT_THROW(format_error("number is too big"));
|
||||||
specs.width = value;
|
specs.width = value;
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
@@ -443,71 +353,104 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
|||||||
// Parse width.
|
// Parse width.
|
||||||
if (it != end) {
|
if (it != end) {
|
||||||
if (*it >= '0' && *it <= '9') {
|
if (*it >= '0' && *it <= '9') {
|
||||||
internal::error_handler eh;
|
specs.width = parse_nonnegative_int(it, end, -1);
|
||||||
specs.width = parse_nonnegative_int(it, end, eh);
|
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
|
||||||
} else if (*it == '*') {
|
} else if (*it == '*') {
|
||||||
++it;
|
++it;
|
||||||
specs.width = static_cast<int>(visit_format_arg(
|
specs.width = static_cast<int>(visit_format_arg(
|
||||||
internal::printf_width_handler<char_type>(specs), get_arg()));
|
detail::printf_width_handler<Char>(specs), get_arg(-1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arg_index;
|
return arg_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename OutputIt, typename Char>
|
template <typename Char, typename Context>
|
||||||
template <typename ArgFormatter>
|
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
basic_format_args<Context> args) {
|
||||||
auto out = this->out();
|
using OutputIt = buffer_appender<Char>;
|
||||||
const Char* start = parse_ctx_.begin();
|
auto out = OutputIt(buf);
|
||||||
const Char* end = parse_ctx_.end();
|
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
||||||
|
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
||||||
|
|
||||||
|
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||||
|
// argument.
|
||||||
|
auto get_arg = [&](int arg_index) {
|
||||||
|
if (arg_index < 0)
|
||||||
|
arg_index = parse_ctx.next_arg_id();
|
||||||
|
else
|
||||||
|
parse_ctx.check_arg_id(--arg_index);
|
||||||
|
return detail::get_arg(context, arg_index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Char* start = parse_ctx.begin();
|
||||||
|
const Char* end = parse_ctx.end();
|
||||||
auto it = start;
|
auto it = start;
|
||||||
while (it != end) {
|
while (it != end) {
|
||||||
char_type c = *it++;
|
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||||
if (c != '%') continue;
|
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Char c = *it++;
|
||||||
if (it != end && *it == c) {
|
if (it != end && *it == c) {
|
||||||
out = std::copy(start, it, out);
|
out = detail::write(
|
||||||
|
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||||
start = ++it;
|
start = ++it;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
out = std::copy(start, it - 1, out);
|
out = detail::write(out, basic_string_view<Char>(
|
||||||
|
start, detail::to_unsigned(it - 1 - start)));
|
||||||
|
|
||||||
format_specs specs;
|
basic_format_specs<Char> specs;
|
||||||
specs.align = align::right;
|
specs.align = align::right;
|
||||||
|
|
||||||
// Parse argument index, flags and width.
|
// Parse argument index, flags and width.
|
||||||
int arg_index = parse_header(it, end, specs);
|
int arg_index = parse_header(it, end, specs, get_arg);
|
||||||
if (arg_index == 0) on_error("argument index out of range");
|
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
||||||
|
|
||||||
// Parse precision.
|
// Parse precision.
|
||||||
if (it != end && *it == '.') {
|
if (it != end && *it == '.') {
|
||||||
++it;
|
++it;
|
||||||
c = it != end ? *it : 0;
|
c = it != end ? *it : 0;
|
||||||
if ('0' <= c && c <= '9') {
|
if ('0' <= c && c <= '9') {
|
||||||
internal::error_handler eh;
|
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
|
||||||
} else if (c == '*') {
|
} else if (c == '*') {
|
||||||
++it;
|
++it;
|
||||||
specs.precision = static_cast<int>(
|
specs.precision = static_cast<int>(
|
||||||
visit_format_arg(internal::printf_precision_handler(), get_arg()));
|
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
||||||
} else {
|
} else {
|
||||||
specs.precision = 0;
|
specs.precision = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
format_arg arg = get_arg(arg_index);
|
auto arg = get_arg(arg_index);
|
||||||
if (specs.alt && visit_format_arg(internal::is_zero_int(), arg))
|
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||||
|
// specified, the '0' flag is ignored
|
||||||
|
if (specs.precision >= 0 && arg.is_integral())
|
||||||
|
specs.fill[0] =
|
||||||
|
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||||
|
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||||
|
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||||
|
auto str_end = str + specs.precision;
|
||||||
|
auto nul = std::find(str, str_end, Char());
|
||||||
|
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
||||||
|
basic_string_view<Char>(
|
||||||
|
str, detail::to_unsigned(nul != str_end ? nul - str
|
||||||
|
: specs.precision)));
|
||||||
|
}
|
||||||
|
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||||
specs.alt = false;
|
specs.alt = false;
|
||||||
if (specs.fill[0] == '0') {
|
if (specs.fill[0] == '0') {
|
||||||
if (arg.is_arithmetic())
|
if (arg.is_arithmetic() && specs.align != align::left)
|
||||||
specs.align = align::numeric;
|
specs.align = align::numeric;
|
||||||
else
|
else
|
||||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types.
|
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||||
|
// flag is also present.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse length and convert the argument to the required type.
|
// Parse length and convert the argument to the required type.
|
||||||
c = it != end ? *it++ : 0;
|
c = it != end ? *it++ : 0;
|
||||||
char_type t = it != end ? *it : 0;
|
Char t = it != end ? *it : 0;
|
||||||
using internal::convert_arg;
|
using detail::convert_arg;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'h':
|
case 'h':
|
||||||
if (t == 'h') {
|
if (t == 'h') {
|
||||||
@@ -531,7 +474,7 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
|||||||
convert_arg<intmax_t>(arg, t);
|
convert_arg<intmax_t>(arg, t);
|
||||||
break;
|
break;
|
||||||
case 'z':
|
case 'z':
|
||||||
convert_arg<std::size_t>(arg, t);
|
convert_arg<size_t>(arg, t);
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
convert_arg<std::ptrdiff_t>(arg, t);
|
convert_arg<std::ptrdiff_t>(arg, t);
|
||||||
@@ -556,7 +499,8 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
|||||||
specs.type = 'd';
|
specs.type = 'd';
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
visit_format_arg(internal::char_converter<basic_printf_context>(arg),
|
visit_format_arg(
|
||||||
|
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
||||||
arg);
|
arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -565,15 +509,16 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
|||||||
start = it;
|
start = it;
|
||||||
|
|
||||||
// Format argument.
|
// Format argument.
|
||||||
visit_format_arg(ArgFormatter(out, specs, *this), arg);
|
out = visit_format_arg(
|
||||||
|
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
||||||
}
|
}
|
||||||
return std::copy(start, it, out);
|
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||||
}
|
}
|
||||||
|
FMT_END_DETAIL_NAMESPACE
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
using basic_printf_context_t =
|
using basic_printf_context_t =
|
||||||
basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>,
|
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||||
Char>;
|
|
||||||
|
|
||||||
using printf_context = basic_printf_context_t<char>;
|
using printf_context = basic_printf_context_t<char>;
|
||||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||||
@@ -587,9 +532,9 @@ using wprintf_args = basic_format_args<wprintf_context>;
|
|||||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
inline format_arg_store<printf_context, Args...> make_printf_args(
|
inline auto make_printf_args(const T&... args)
|
||||||
const Args&... args) {
|
-> format_arg_store<printf_context, T...> {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,18 +544,19 @@ inline format_arg_store<printf_context, Args...> make_printf_args(
|
|||||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... Args>
|
template <typename... T>
|
||||||
inline format_arg_store<wprintf_context, Args...> make_wprintf_args(
|
inline auto make_wprintf_args(const T&... args)
|
||||||
const Args&... args) {
|
-> format_arg_store<wprintf_context, T...> {
|
||||||
return {args...};
|
return {args...};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline std::basic_string<Char> vsprintf(
|
inline auto vsprintf(
|
||||||
const S& format,
|
const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
printf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
return to_string(buffer);
|
return to_string(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,20 +569,21 @@ inline std::basic_string<Char> vsprintf(
|
|||||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T,
|
||||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||||
inline std::basic_string<Char> sprintf(const S& format, const Args&... args) {
|
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||||
using context = basic_printf_context_t<Char>;
|
using context = basic_printf_context_t<Char>;
|
||||||
return vsprintf(to_string_view(format), make_format_args<context>(args...));
|
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vfprintf(
|
inline auto vfprintf(
|
||||||
std::FILE* f, const S& format,
|
std::FILE* f, const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> int {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
printf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
std::size_t size = buffer.size();
|
size_t size = buffer.size();
|
||||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||||
? -1
|
? -1
|
||||||
: static_cast<int>(size);
|
: static_cast<int>(size);
|
||||||
@@ -651,19 +598,19 @@ inline int vfprintf(
|
|||||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T, typename Char = char_t<S>>
|
||||||
typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>>
|
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||||
inline int fprintf(std::FILE* f, const S& format, const Args&... args) {
|
|
||||||
using context = basic_printf_context_t<Char>;
|
using context = basic_printf_context_t<Char>;
|
||||||
return vfprintf(f, to_string_view(format),
|
return vfprintf(f, to_string_view(fmt),
|
||||||
make_format_args<context>(args...));
|
fmt::make_format_args<context>(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vprintf(
|
inline auto vprintf(
|
||||||
const S& format,
|
const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
return vfprintf(stdout, to_string_view(format), args);
|
-> int {
|
||||||
|
return vfprintf(stdout, to_string_view(fmt), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -675,52 +622,31 @@ inline int vprintf(
|
|||||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename S, typename... Args,
|
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
FMT_ENABLE_IF(internal::is_string<S>::value)>
|
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||||
inline int printf(const S& format_str, const Args&... args) {
|
return vprintf(
|
||||||
using context = basic_printf_context_t<char_t<S>>;
|
to_string_view(fmt),
|
||||||
return vprintf(to_string_view(format_str),
|
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename S, typename Char = char_t<S>>
|
template <typename S, typename Char = char_t<S>>
|
||||||
inline int vfprintf(
|
FMT_DEPRECATED auto vfprintf(
|
||||||
std::basic_ostream<Char>& os, const S& format,
|
std::basic_ostream<Char>& os, const S& fmt,
|
||||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args) {
|
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||||
|
-> int {
|
||||||
basic_memory_buffer<Char> buffer;
|
basic_memory_buffer<Char> buffer;
|
||||||
printf(buffer, to_string_view(format), args);
|
vprintf(buffer, to_string_view(fmt), args);
|
||||||
internal::write(os, buffer);
|
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||||
return static_cast<int>(buffer.size());
|
return static_cast<int>(buffer.size());
|
||||||
}
|
}
|
||||||
|
template <typename S, typename... T, typename Char = char_t<S>>
|
||||||
/** Formats arguments and writes the output to the range. */
|
FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
|
||||||
template <typename ArgFormatter, typename Char,
|
const T&... args) -> int {
|
||||||
typename Context =
|
return vfprintf(os, to_string_view(fmt),
|
||||||
basic_printf_context<typename ArgFormatter::iterator, Char>>
|
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
|
||||||
typename ArgFormatter::iterator vprintf(
|
|
||||||
internal::buffer<Char>& out, basic_string_view<Char> format_str,
|
|
||||||
basic_format_args<type_identity_t<Context>> args) {
|
|
||||||
typename ArgFormatter::iterator iter(out);
|
|
||||||
Context(iter, format_str, args).template format<ArgFormatter>();
|
|
||||||
return iter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
FMT_MODULE_EXPORT_END
|
||||||
\rst
|
|
||||||
Prints formatted data to the stream *os*.
|
|
||||||
|
|
||||||
**Example**::
|
|
||||||
|
|
||||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
|
||||||
\endrst
|
|
||||||
*/
|
|
||||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
|
||||||
inline int fprintf(std::basic_ostream<Char>& os, const S& format_str,
|
|
||||||
const Args&... args) {
|
|
||||||
using context = basic_printf_context_t<Char>;
|
|
||||||
return vfprintf(os, to_string_view(format_str),
|
|
||||||
make_format_args<context>(args...));
|
|
||||||
}
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_PRINTF_H_
|
#endif // FMT_PRINTF_H_
|
||||||
|
|||||||
@@ -17,44 +17,34 @@
|
|||||||
|
|
||||||
#include "format.h"
|
#include "format.h"
|
||||||
|
|
||||||
// output only up to N items from the range.
|
|
||||||
#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT
|
|
||||||
# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
template <typename Char> struct formatting_base {
|
template <typename Char, typename Enable = void> struct formatting_range {
|
||||||
|
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||||
|
Char prefix = '{';
|
||||||
|
Char postfix = '}';
|
||||||
|
#else
|
||||||
|
Char prefix = '[';
|
||||||
|
Char postfix = ']';
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Enable = void>
|
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||||
struct formatting_range : formatting_base<Char> {
|
Char prefix = '(';
|
||||||
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
|
Char postfix = ')';
|
||||||
FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the
|
|
||||||
// range.
|
template <typename ParseContext>
|
||||||
Char prefix;
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
Char delimiter;
|
return ctx.begin();
|
||||||
Char postfix;
|
}
|
||||||
formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename Enable = void>
|
namespace detail {
|
||||||
struct formatting_tuple : formatting_base<Char> {
|
|
||||||
Char prefix;
|
|
||||||
Char delimiter;
|
|
||||||
Char postfix;
|
|
||||||
formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
|
|
||||||
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace internal {
|
|
||||||
|
|
||||||
template <typename RangeT, typename OutputIterator>
|
template <typename RangeT, typename OutputIterator>
|
||||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||||
@@ -75,8 +65,14 @@ OutputIterator copy(char ch, OutputIterator out) {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename OutputIterator>
|
||||||
|
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||||
|
*out++ = ch;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/// Return true value if T has std::string interface, like std::string_view.
|
/// Return true value if T has std::string interface, like std::string_view.
|
||||||
template <typename T> class is_like_std_string {
|
template <typename T> class is_std_string_like {
|
||||||
template <typename U>
|
template <typename U>
|
||||||
static auto check(U* p)
|
static auto check(U* p)
|
||||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||||
@@ -88,19 +84,107 @@ template <typename T> class is_like_std_string {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char>
|
template <typename Char>
|
||||||
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
|
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||||
|
|
||||||
template <typename... Ts> struct conditional_helper {};
|
template <typename... Ts> struct conditional_helper {};
|
||||||
|
|
||||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||||
|
|
||||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||||
|
|
||||||
|
# define FMT_DECLTYPE_RETURN(val) \
|
||||||
|
->decltype(val) { return val; } \
|
||||||
|
static_assert( \
|
||||||
|
true, "") // This makes it so that a semicolon is required after the
|
||||||
|
// macro, which helps clang-format handle the formatting.
|
||||||
|
|
||||||
|
// C array overload
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
auto range_end(const T (&arr)[N]) -> const T* {
|
||||||
|
return arr + N;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_member_fn_begin_end_t : std::false_type {};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct is_range_<
|
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||||
T, conditional_t<false,
|
decltype(std::declval<T>().end())>>
|
||||||
conditional_helper<decltype(std::declval<T>().begin()),
|
: std::true_type {};
|
||||||
decltype(std::declval<T>().end())>,
|
|
||||||
void>> : std::true_type {};
|
// Member function overload
|
||||||
|
template <typename T>
|
||||||
|
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||||
|
template <typename T>
|
||||||
|
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||||
|
|
||||||
|
// ADL overload. Only participates in overload resolution if member functions
|
||||||
|
// are not found.
|
||||||
|
template <typename T>
|
||||||
|
auto range_begin(T&& rng)
|
||||||
|
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||||
|
decltype(begin(static_cast<T&&>(rng)))> {
|
||||||
|
return begin(static_cast<T&&>(rng));
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||||
|
decltype(end(static_cast<T&&>(rng)))> {
|
||||||
|
return end(static_cast<T&&>(rng));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_const_begin_end : std::false_type {};
|
||||||
|
template <typename T, typename Enable = void>
|
||||||
|
struct has_mutable_begin_end : std::false_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_const_begin_end<
|
||||||
|
T, void_t<decltype(detail::range_begin(
|
||||||
|
std::declval<const remove_cvref_t<T>&>())),
|
||||||
|
decltype(detail::range_begin(
|
||||||
|
std::declval<const remove_cvref_t<T>&>()))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct has_mutable_begin_end<
|
||||||
|
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||||
|
decltype(detail::range_begin(std::declval<T>())),
|
||||||
|
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct is_range_<T, void>
|
||||||
|
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||||
|
has_mutable_begin_end<T>::value)> {};
|
||||||
|
|
||||||
|
template <typename T, typename Enable = void> struct range_to_view;
|
||||||
|
template <typename T>
|
||||||
|
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
|
||||||
|
struct view_t {
|
||||||
|
const T* m_range_ptr;
|
||||||
|
|
||||||
|
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
|
||||||
|
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
|
||||||
|
};
|
||||||
|
static auto view(const T& range) -> view_t { return {&range}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
|
||||||
|
has_mutable_begin_end<T>::value>> {
|
||||||
|
struct view_t {
|
||||||
|
T m_range_copy;
|
||||||
|
|
||||||
|
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
|
||||||
|
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
|
||||||
|
};
|
||||||
|
static auto view(const T& range) -> view_t { return {range}; }
|
||||||
|
};
|
||||||
|
# undef FMT_DECLTYPE_RETURN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// tuple_size and tuple_element check.
|
/// tuple_size and tuple_element check.
|
||||||
@@ -118,26 +202,24 @@ template <typename T> class is_tuple_like_ {
|
|||||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||||
template <typename T, T... N>
|
template <typename T, T... N>
|
||||||
using integer_sequence = std::integer_sequence<T, N...>;
|
using integer_sequence = std::integer_sequence<T, N...>;
|
||||||
template <std::size_t... N> using index_sequence = std::index_sequence<N...>;
|
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||||
template <std::size_t N>
|
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||||
using make_index_sequence = std::make_index_sequence<N>;
|
|
||||||
#else
|
#else
|
||||||
template <typename T, T... N> struct integer_sequence {
|
template <typename T, T... N> struct integer_sequence {
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
|
|
||||||
static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); }
|
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||||
};
|
};
|
||||||
|
|
||||||
template <std::size_t... N>
|
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||||
using index_sequence = integer_sequence<std::size_t, N...>;
|
|
||||||
|
|
||||||
template <typename T, std::size_t N, T... Ns>
|
template <typename T, size_t N, T... Ns>
|
||||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||||
template <typename T, T... Ns>
|
template <typename T, T... Ns>
|
||||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||||
|
|
||||||
template <std::size_t N>
|
template <size_t N>
|
||||||
using make_index_sequence = make_integer_sequence<std::size_t, N>;
|
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <class Tuple, class F, size_t... Is>
|
template <class Tuple, class F, size_t... Is>
|
||||||
@@ -159,37 +241,48 @@ template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
|||||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Arg, FMT_ENABLE_IF(!is_like_std_string<
|
template <typename Range>
|
||||||
typename std::decay<Arg>::type>::value)>
|
using value_type =
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||||
return add_space ? " {}" : "{}";
|
|
||||||
|
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||||
|
*out++ = ',';
|
||||||
|
*out++ = ' ';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Arg, FMT_ENABLE_IF(is_like_std_string<
|
template <
|
||||||
typename std::decay<Arg>::type>::value)>
|
typename Char, typename OutputIt, typename Arg,
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) {
|
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
|
||||||
return add_space ? " \"{}\"" : "\"{}\"";
|
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||||
|
*out++ = '"';
|
||||||
|
out = write<Char>(out, v);
|
||||||
|
*out++ = '"';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
|
template <typename Char, typename OutputIt, typename Arg,
|
||||||
return add_space ? " \"{}\"" : "\"{}\"";
|
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||||
}
|
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
|
*out++ = '\'';
|
||||||
return add_space ? L" \"{}\"" : L"\"{}\"";
|
*out++ = v;
|
||||||
|
*out++ = '\'';
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
|
template <
|
||||||
return add_space ? " '{}'" : "'{}'";
|
typename Char, typename OutputIt, typename Arg,
|
||||||
}
|
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||||
FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
|
!std::is_same<Arg, Char>::value)>
|
||||||
return add_space ? L" '{}'" : L"'{}'";
|
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||||
|
return write<Char>(out, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace internal
|
} // namespace detail
|
||||||
|
|
||||||
template <typename T> struct is_tuple_like {
|
template <typename T> struct is_tuple_like {
|
||||||
static FMT_CONSTEXPR_DECL const bool value =
|
static FMT_CONSTEXPR_DECL const bool value =
|
||||||
internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
|
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename TupleT, typename Char>
|
template <typename TupleT, typename Char>
|
||||||
@@ -198,23 +291,14 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
|||||||
// C++11 generic lambda for format()
|
// C++11 generic lambda for format()
|
||||||
template <typename FormatContext> struct format_each {
|
template <typename FormatContext> struct format_each {
|
||||||
template <typename T> void operator()(const T& v) {
|
template <typename T> void operator()(const T& v) {
|
||||||
if (i > 0) {
|
if (i > 0) out = detail::write_delimiter(out);
|
||||||
if (formatting.add_prepostfix_space) {
|
out = detail::write_range_entry<Char>(out, v);
|
||||||
*out++ = ' ';
|
|
||||||
}
|
|
||||||
out = internal::copy(formatting.delimiter, out);
|
|
||||||
}
|
|
||||||
out = format_to(out,
|
|
||||||
internal::format_str_quoted(
|
|
||||||
(formatting.add_delimiter_spaces && i > 0), v),
|
|
||||||
v);
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
formatting_tuple<Char>& formatting;
|
formatting_tuple<Char>& formatting;
|
||||||
std::size_t& i;
|
size_t& i;
|
||||||
typename std::add_lvalue_reference<decltype(
|
typename std::add_lvalue_reference<
|
||||||
std::declval<FormatContext>().out())>::type out;
|
decltype(std::declval<FormatContext>().out())>::type out;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -228,14 +312,11 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
|||||||
template <typename FormatContext = format_context>
|
template <typename FormatContext = format_context>
|
||||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||||
auto out = ctx.out();
|
auto out = ctx.out();
|
||||||
std::size_t i = 0;
|
size_t i = 0;
|
||||||
internal::copy(formatting.prefix, out);
|
|
||||||
|
|
||||||
internal::for_each(values, format_each<FormatContext>{formatting, i, out});
|
detail::copy(formatting.prefix, out);
|
||||||
if (formatting.add_prepostfix_space) {
|
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||||
*out++ = ' ';
|
detail::copy(formatting.postfix, out);
|
||||||
}
|
|
||||||
internal::copy(formatting.postfix, out);
|
|
||||||
|
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
}
|
}
|
||||||
@@ -243,15 +324,22 @@ struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
|||||||
|
|
||||||
template <typename T, typename Char> struct is_range {
|
template <typename T, typename Char> struct is_range {
|
||||||
static FMT_CONSTEXPR_DECL const bool value =
|
static FMT_CONSTEXPR_DECL const bool value =
|
||||||
internal::is_range_<T>::value &&
|
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||||
!internal::is_like_std_string<T>::value &&
|
|
||||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||||
!std::is_constructible<internal::std_string_view<Char>, T>::value;
|
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename RangeT, typename Char>
|
template <typename T, typename Char>
|
||||||
struct formatter<RangeT, Char,
|
struct formatter<
|
||||||
enable_if_t<fmt::is_range<RangeT, Char>::value>> {
|
T, Char,
|
||||||
|
enable_if_t<
|
||||||
|
fmt::is_range<T, Char>::value
|
||||||
|
// Workaround a bug in MSVC 2017 and earlier.
|
||||||
|
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||||
|
&& (has_formatter<detail::value_type<T>, format_context>::value ||
|
||||||
|
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||||
|
#endif
|
||||||
|
>> {
|
||||||
formatting_range<Char> formatting;
|
formatting_range<Char> formatting;
|
||||||
|
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
@@ -260,73 +348,68 @@ struct formatter<RangeT, Char,
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format(const RangeT& values,
|
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
|
||||||
FormatContext& ctx) {
|
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||||
auto out = internal::copy(formatting.prefix, ctx.out());
|
size_t i = 0;
|
||||||
std::size_t i = 0;
|
auto view = detail::range_to_view<T>::view(values);
|
||||||
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
|
auto it = view.begin();
|
||||||
if (i > 0) {
|
auto end = view.end();
|
||||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
for (; it != end; ++it) {
|
||||||
out = internal::copy(formatting.delimiter, out);
|
if (i > 0) out = detail::write_delimiter(out);
|
||||||
|
out = detail::write_range_entry<Char>(out, *it);
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
out = format_to(out,
|
return detail::copy(formatting.postfix, out);
|
||||||
internal::format_str_quoted(
|
|
||||||
(formatting.add_delimiter_spaces && i > 0), *it),
|
|
||||||
*it);
|
|
||||||
if (++i > formatting.range_length_limit) {
|
|
||||||
out = format_to(out, " ... <other elements>");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (formatting.add_prepostfix_space) *out++ = ' ';
|
|
||||||
return internal::copy(formatting.postfix, out);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T> struct tuple_arg_join : internal::view {
|
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||||
const std::tuple<T...>& tuple;
|
const std::tuple<T...>& tuple;
|
||||||
basic_string_view<Char> sep;
|
basic_string_view<Char> sep;
|
||||||
|
|
||||||
tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
|
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||||
: tuple{t}, sep{s} {}
|
: tuple(t), sep{s} {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename... T>
|
template <typename Char, typename... T>
|
||||||
struct formatter<tuple_arg_join<Char, T...>, Char> {
|
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||||
|
|
||||||
|
template <typename Char, typename... T>
|
||||||
|
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||||
template <typename ParseContext>
|
template <typename ParseContext>
|
||||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||||
return ctx.begin();
|
return ctx.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format(
|
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
|
typename FormatContext::iterator {
|
||||||
return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
|
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <typename FormatContext, size_t... N>
|
template <typename FormatContext, size_t... N>
|
||||||
typename FormatContext::iterator format(
|
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
detail::index_sequence<N...>) ->
|
||||||
internal::index_sequence<N...>) {
|
typename FormatContext::iterator {
|
||||||
return format_args(value, ctx, std::get<N>(value.tuple)...);
|
using std::get;
|
||||||
|
return format_args(value, ctx, get<N>(value.tuple)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext>
|
template <typename FormatContext>
|
||||||
typename FormatContext::iterator format_args(
|
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
|
||||||
const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
|
typename FormatContext::iterator {
|
||||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||||
// can be replaced with a constexpr branch in the variadic overload.
|
// can be replaced with a constexpr branch in the variadic overload.
|
||||||
return ctx.out();
|
return ctx.out();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename FormatContext, typename Arg, typename... Args>
|
template <typename FormatContext, typename Arg, typename... Args>
|
||||||
typename FormatContext::iterator format_args(
|
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||||
const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
|
const Arg& arg, const Args&... args) ->
|
||||||
const Arg& arg, const Args&... args) {
|
typename FormatContext::iterator {
|
||||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||||
auto out = ctx.out();
|
auto out = base().format(arg, ctx);
|
||||||
out = base{}.format(arg, ctx);
|
|
||||||
if (sizeof...(Args) > 0) {
|
if (sizeof...(Args) > 0) {
|
||||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||||
ctx.advance_to(out);
|
ctx.advance_to(out);
|
||||||
@@ -336,6 +419,8 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
/**
|
/**
|
||||||
\rst
|
\rst
|
||||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||||
@@ -348,14 +433,15 @@ struct formatter<tuple_arg_join<Char, T...>, Char> {
|
|||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
|
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||||
string_view sep) {
|
-> tuple_join_view<char, T...> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... T>
|
template <typename... T>
|
||||||
FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||||
wstring_view sep) {
|
basic_string_view<wchar_t> sep)
|
||||||
|
-> tuple_join_view<wchar_t, T...> {
|
||||||
return {tuple, sep};
|
return {tuple, sep};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,17 +457,12 @@ FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
|
|||||||
\endrst
|
\endrst
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, char> join(
|
auto join(std::initializer_list<T> list, string_view sep)
|
||||||
std::initializer_list<T> list, string_view sep) {
|
-> join_view<const T*, const T*> {
|
||||||
return join(std::begin(list), std::end(list), sep);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
arg_join<internal::iterator_t<const std::initializer_list<T>>, wchar_t> join(
|
|
||||||
std::initializer_list<T> list, wstring_view sep) {
|
|
||||||
return join(std::begin(list), std::end(list), sep);
|
return join(std::begin(list), std::end(list), sep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // FMT_RANGES_H_
|
#endif // FMT_RANGES_H_
|
||||||
|
|||||||
236
include/spdlog/fmt/bundled/xchar.h
Normal file
236
include/spdlog/fmt/bundled/xchar.h
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
// Formatting library for C++ - optional wchar_t and exotic character support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 - present, Victor Zverovich
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// For the license information refer to format.h.
|
||||||
|
|
||||||
|
#ifndef FMT_WCHAR_H_
|
||||||
|
#define FMT_WCHAR_H_
|
||||||
|
|
||||||
|
#include <cwchar>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
|
|
||||||
|
FMT_BEGIN_NAMESPACE
|
||||||
|
namespace detail {
|
||||||
|
template <typename T>
|
||||||
|
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||||
|
}
|
||||||
|
|
||||||
|
FMT_MODULE_EXPORT_BEGIN
|
||||||
|
|
||||||
|
using wstring_view = basic_string_view<wchar_t>;
|
||||||
|
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||||
|
using wformat_context = buffer_context<wchar_t>;
|
||||||
|
using wformat_args = basic_format_args<wformat_context>;
|
||||||
|
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||||
|
|
||||||
|
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||||
|
// Workaround broken conversion on older gcc.
|
||||||
|
template <typename... Args> using wformat_string = wstring_view;
|
||||||
|
#else
|
||||||
|
template <typename... Args>
|
||||||
|
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <> struct is_char<wchar_t> : std::true_type {};
|
||||||
|
template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||||
|
template <> struct is_char<char16_t> : std::true_type {};
|
||||||
|
template <> struct is_char<char32_t> : std::true_type {};
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||||
|
const Args&... args) {
|
||||||
|
return {args...};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline namespace literals {
|
||||||
|
constexpr auto operator"" _format(const wchar_t* s, size_t n)
|
||||||
|
-> detail::udl_formatter<wchar_t> {
|
||||||
|
return {{s, n}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||||
|
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||||
|
return {s};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // namespace literals
|
||||||
|
|
||||||
|
template <typename It, typename Sentinel>
|
||||||
|
auto join(It begin, Sentinel end, wstring_view sep)
|
||||||
|
-> join_view<It, Sentinel, wchar_t> {
|
||||||
|
return {begin, end, sep};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Range>
|
||||||
|
auto join(Range&& range, wstring_view sep)
|
||||||
|
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||||
|
wchar_t> {
|
||||||
|
return join(std::begin(range), std::end(range), sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
auto join(std::initializer_list<T> list, wstring_view sep)
|
||||||
|
-> join_view<const T*, const T*, wchar_t> {
|
||||||
|
return join(std::begin(list), std::end(list), sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
|
auto vformat(basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
basic_memory_buffer<Char> buffer;
|
||||||
|
detail::vformat_to(buffer, format_str, args);
|
||||||
|
return to_string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass char_t as a default template parameter instead of using
|
||||||
|
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||||
|
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
return vformat(to_string_view(format_str), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat(
|
||||||
|
const Locale& loc, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
return detail::vformat(loc, to_string_view(format_str), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||||
|
-> std::basic_string<Char> {
|
||||||
|
return detail::vformat(loc, to_string_view(format_str),
|
||||||
|
fmt::make_args_checked<Args...>(format_str, args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
auto vformat_to(OutputIt out, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> OutputIt {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
return vformat_to(out, to_string_view(fmt), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args, typename Char, size_t SIZE,
|
||||||
|
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||||
|
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
|
||||||
|
const S& format_str, Args&&... args) ->
|
||||||
|
typename buffer_context<Char>::iterator {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
detail::vformat_to(buf, to_string_view(format_str), vargs, {});
|
||||||
|
return detail::buffer_appender<Char>(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_locale<Locale>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat_to(
|
||||||
|
OutputIt out, const Locale& loc, const S& format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
|
||||||
|
auto&& buf = detail::get_buffer<Char>(out);
|
||||||
|
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||||
|
return detail::get_iterator(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <
|
||||||
|
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||||
|
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||||
|
Args&&... args) ->
|
||||||
|
typename std::enable_if<enable, OutputIt>::type {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||||
|
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename Char, typename... Args,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto vformat_to_n(
|
||||||
|
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||||
|
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||||
|
-> format_to_n_result<OutputIt> {
|
||||||
|
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||||
|
n);
|
||||||
|
detail::vformat_to(buf, format_str, args);
|
||||||
|
return {buf.out(), buf.count()};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OutputIt, typename S, typename... Args,
|
||||||
|
typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||||
|
detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||||
|
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
return vformat_to_n(out, n, to_string_view(fmt), vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||||
|
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||||
|
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||||
|
detail::counting_buffer<Char> buf;
|
||||||
|
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||||
|
detail::vformat_to(buf, to_string_view(fmt), vargs);
|
||||||
|
return buf.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||||
|
wmemory_buffer buffer;
|
||||||
|
detail::vformat_to(buffer, fmt, args);
|
||||||
|
buffer.push_back(L'\0');
|
||||||
|
if (std::fputws(buffer.data(), f) == -1)
|
||||||
|
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||||
|
vprint(stdout, fmt, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T>
|
||||||
|
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||||
|
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||||
|
*/
|
||||||
|
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||||
|
return format(FMT_STRING(L"{}"), value);
|
||||||
|
}
|
||||||
|
FMT_MODULE_EXPORT_END
|
||||||
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
|
#endif // FMT_WCHAR_H_
|
||||||
20
include/spdlog/fmt/chrono.h
Normal file
20
include/spdlog/fmt/chrono.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright(c) 2016 Gabi Melman.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
//
|
||||||
|
// include bundled or external copy of fmtlib's chrono support
|
||||||
|
//
|
||||||
|
|
||||||
|
#if !defined(SPDLOG_FMT_EXTERNAL)
|
||||||
|
# ifdef SPDLOG_HEADER_ONLY
|
||||||
|
# ifndef FMT_HEADER_ONLY
|
||||||
|
# define FMT_HEADER_ONLY
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
# include <spdlog/fmt/bundled/chrono.h>
|
||||||
|
#else
|
||||||
|
# include <fmt/chrono.h>
|
||||||
|
#endif
|
||||||
20
include/spdlog/fmt/compile.h
Normal file
20
include/spdlog/fmt/compile.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright(c) 2016 Gabi Melman.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
//
|
||||||
|
// include bundled or external copy of fmtlib's ostream support
|
||||||
|
//
|
||||||
|
|
||||||
|
#if !defined(SPDLOG_FMT_EXTERNAL)
|
||||||
|
# ifdef SPDLOG_HEADER_ONLY
|
||||||
|
# ifndef FMT_HEADER_ONLY
|
||||||
|
# define FMT_HEADER_ONLY
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
# include <spdlog/fmt/bundled/compile.h>
|
||||||
|
#else
|
||||||
|
# include <fmt/compile.h>
|
||||||
|
#endif
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
# ifndef FMT_USE_WINDOWS_H
|
# ifndef FMT_USE_WINDOWS_H
|
||||||
# define FMT_USE_WINDOWS_H 0
|
# define FMT_USE_WINDOWS_H 0
|
||||||
# endif
|
# endif
|
||||||
|
// enable the 'n' flag in for backward compatibility with fmt 6.x
|
||||||
|
# define FMT_DEPRECATED_N_SPECIFIER
|
||||||
# include <spdlog/fmt/bundled/core.h>
|
# 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
|
||||||
|
|||||||
20
include/spdlog/fmt/xchar.h
Normal file
20
include/spdlog/fmt/xchar.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// Copyright(c) 2016 Gabi Melman.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
//
|
||||||
|
// include bundled or external copy of fmtlib's ostream support
|
||||||
|
//
|
||||||
|
|
||||||
|
#if !defined(SPDLOG_FMT_EXTERNAL)
|
||||||
|
# ifdef SPDLOG_HEADER_ONLY
|
||||||
|
# ifndef FMT_HEADER_ONLY
|
||||||
|
# define FMT_HEADER_ONLY
|
||||||
|
# endif
|
||||||
|
# endif
|
||||||
|
# include <spdlog/fmt/bundled/xchar.h>
|
||||||
|
#else
|
||||||
|
# include <fmt/xchar.h>
|
||||||
|
#endif
|
||||||
@@ -247,7 +247,11 @@ SPDLOG_INLINE void logger::err_handler_(const std::string &msg)
|
|||||||
auto tm_time = details::os::localtime(system_clock::to_time_t(now));
|
auto tm_time = details::os::localtime(system_clock::to_time_t(now));
|
||||||
char date_buf[64];
|
char date_buf[64];
|
||||||
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
|
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
|
||||||
fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
|
#if defined(USING_R) && defined(R_R_H) // if in R environment
|
||||||
|
REprintf("[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
|
||||||
|
#else
|
||||||
|
std::fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace spdlog
|
} // namespace spdlog
|
||||||
|
|||||||
@@ -19,10 +19,14 @@
|
|||||||
#include <spdlog/details/backtracer.h>
|
#include <spdlog/details/backtracer.h>
|
||||||
|
|
||||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
|
# ifndef _WIN32
|
||||||
|
# error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
|
||||||
|
# endif
|
||||||
# include <spdlog/details/os.h>
|
# include <spdlog/details/os.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifndef SPDLOG_NO_EXCEPTIONS
|
#ifndef SPDLOG_NO_EXCEPTIONS
|
||||||
# define SPDLOG_LOGGER_CATCH() \
|
# define SPDLOG_LOGGER_CATCH() \
|
||||||
catch (const std::exception &ex) \
|
catch (const std::exception &ex) \
|
||||||
@@ -31,7 +35,8 @@
|
|||||||
} \
|
} \
|
||||||
catch (...) \
|
catch (...) \
|
||||||
{ \
|
{ \
|
||||||
err_handler_("Unknown exception in logger"); \
|
err_handler_("Rethrowing unknown exception in logger"); \
|
||||||
|
throw; \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
# define SPDLOG_LOGGER_CATCH()
|
# define SPDLOG_LOGGER_CATCH()
|
||||||
@@ -70,63 +75,18 @@ public:
|
|||||||
logger(const logger &other);
|
logger(const logger &other);
|
||||||
logger(logger &&other) SPDLOG_NOEXCEPT;
|
logger(logger &&other) SPDLOG_NOEXCEPT;
|
||||||
logger &operator=(logger other) SPDLOG_NOEXCEPT;
|
logger &operator=(logger other) SPDLOG_NOEXCEPT;
|
||||||
|
|
||||||
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT;
|
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT;
|
||||||
|
|
||||||
// FormatString is a type derived from fmt::compile_string
|
template<typename FormatString, typename... Args>
|
||||||
template<typename FormatString, typename std::enable_if<fmt::is_compile_string<FormatString>::value, int>::type = 0, typename... Args>
|
void log(source_loc loc, level::level_enum lvl, const FormatString &fmt, Args &&...args)
|
||||||
void log(source_loc loc, level::level_enum lvl, const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
{
|
||||||
log_(loc, lvl, fmt, args...);
|
log_(loc, lvl, fmt, std::forward<Args>(args)...);
|
||||||
}
|
|
||||||
|
|
||||||
// FormatString is NOT a type derived from fmt::compile_string but is a string_view_t or can be implicitly converted to one
|
|
||||||
template<typename... Args>
|
|
||||||
void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log_(loc, lvl, fmt, args...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
void log(level::level_enum lvl, const FormatString &fmt, const Args &... args)
|
void log(level::level_enum lvl, const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
log(source_loc{}, lvl, fmt, args...);
|
log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void trace(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::trace, fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void debug(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::debug, fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void info(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::info, fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void warn(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::warn, fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void error(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::err, fmt, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
|
||||||
void critical(const FormatString &fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
log(level::critical, fmt, args...);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -135,9 +95,8 @@ public:
|
|||||||
log(source_loc{}, lvl, msg);
|
log(source_loc{}, lvl, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// T can be statically converted to string_view and isn't a fmt::compile_string
|
// T can be statically converted to string_view
|
||||||
template<class T, typename std::enable_if<
|
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, int>::type = 0>
|
||||||
std::is_convertible<const T &, spdlog::string_view_t>::value && !fmt::is_compile_string<T>::value, int>::type = 0>
|
|
||||||
void log(source_loc loc, level::level_enum lvl, const T &msg)
|
void log(source_loc loc, level::level_enum lvl, const T &msg)
|
||||||
{
|
{
|
||||||
log(loc, lvl, string_view_t{msg});
|
log(loc, lvl, string_view_t{msg});
|
||||||
@@ -183,6 +142,42 @@ public:
|
|||||||
log(loc, lvl, "{}", msg);
|
log(loc, lvl, "{}", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void trace(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::trace, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void debug(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::debug, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void info(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::info, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void warn(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::warn, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void error(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::err, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FormatString, typename... Args>
|
||||||
|
void critical(const FormatString &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
log(level::critical, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void trace(const T &msg)
|
void trace(const T &msg)
|
||||||
{
|
{
|
||||||
@@ -219,57 +214,6 @@ public:
|
|||||||
log(level::critical, msg);
|
log(level::critical, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
|
||||||
#ifndef _WIN32
|
|
||||||
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
|
|
||||||
#else
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args)
|
|
||||||
{
|
|
||||||
bool log_enabled = should_log(lvl);
|
|
||||||
bool traceback_enabled = tracer_.enabled();
|
|
||||||
if (!log_enabled && !traceback_enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SPDLOG_TRY
|
|
||||||
{
|
|
||||||
// format to wmemory_buffer and convert to utf8
|
|
||||||
fmt::wmemory_buffer wbuf;
|
|
||||||
fmt::format_to(wbuf, fmt, args...);
|
|
||||||
|
|
||||||
memory_buf_t buf;
|
|
||||||
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf);
|
|
||||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
|
||||||
log_it_(log_msg, log_enabled, traceback_enabled);
|
|
||||||
}
|
|
||||||
SPDLOG_LOGGER_CATCH()
|
|
||||||
}
|
|
||||||
|
|
||||||
// T can be statically converted to wstring_view
|
|
||||||
template<class T, typename std::enable_if<is_convertible_to_wstring_view<const T &>::value, int>::type = 0>
|
|
||||||
void log(source_loc loc, level::level_enum lvl, const T &msg)
|
|
||||||
{
|
|
||||||
bool log_enabled = should_log(lvl);
|
|
||||||
bool traceback_enabled = tracer_.enabled();
|
|
||||||
if (!log_enabled && !traceback_enabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SPDLOG_TRY
|
|
||||||
{
|
|
||||||
memory_buf_t buf;
|
|
||||||
details::os::wstr_to_utf8buf(msg, buf);
|
|
||||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
|
||||||
log_it_(log_msg, log_enabled, traceback_enabled);
|
|
||||||
}
|
|
||||||
SPDLOG_LOGGER_CATCH()
|
|
||||||
}
|
|
||||||
#endif // _WIN32
|
|
||||||
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
|
||||||
|
|
||||||
// return true logging is enabled for the given level.
|
// return true logging is enabled for the given level.
|
||||||
bool should_log(level::level_enum msg_level) const
|
bool should_log(level::level_enum msg_level) const
|
||||||
{
|
{
|
||||||
@@ -325,8 +269,9 @@ protected:
|
|||||||
details::backtracer tracer_;
|
details::backtracer tracer_;
|
||||||
|
|
||||||
// common implementation for after templated public api has been resolved
|
// common implementation for after templated public api has been resolved
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args, typename Char = fmt::char_t<FormatString>,
|
||||||
void log_(source_loc loc, level::level_enum lvl, const FormatString &fmt, const Args &... args)
|
typename std::enable_if<!std::is_same<Char, wchar_t>::value, Char>::type * = nullptr>
|
||||||
|
void log_(source_loc loc, level::level_enum lvl, const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
bool log_enabled = should_log(lvl);
|
bool log_enabled = should_log(lvl);
|
||||||
bool traceback_enabled = tracer_.enabled();
|
bool traceback_enabled = tracer_.enabled();
|
||||||
@@ -337,13 +282,58 @@ protected:
|
|||||||
SPDLOG_TRY
|
SPDLOG_TRY
|
||||||
{
|
{
|
||||||
memory_buf_t buf;
|
memory_buf_t buf;
|
||||||
fmt::format_to(buf, fmt, args...);
|
fmt::detail::vformat_to(buf, fmt::to_string_view(fmt), fmt::make_format_args(args...));
|
||||||
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
||||||
log_it_(log_msg, log_enabled, traceback_enabled);
|
log_it_(log_msg, log_enabled, traceback_enabled);
|
||||||
}
|
}
|
||||||
SPDLOG_LOGGER_CATCH()
|
SPDLOG_LOGGER_CATCH()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
|
template<typename... Args>
|
||||||
|
void log_(source_loc loc, level::level_enum lvl, const wstring_view_t &fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
bool log_enabled = should_log(lvl);
|
||||||
|
bool traceback_enabled = tracer_.enabled();
|
||||||
|
if (!log_enabled && !traceback_enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SPDLOG_TRY
|
||||||
|
{
|
||||||
|
// format to wmemory_buffer and convert to utf8
|
||||||
|
fmt::wmemory_buffer wbuf;
|
||||||
|
fmt::format_to(std::back_inserter(wbuf), fmt, std::forward<Args>(args)...);
|
||||||
|
memory_buf_t buf;
|
||||||
|
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf);
|
||||||
|
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
||||||
|
log_it_(log_msg, log_enabled, traceback_enabled);
|
||||||
|
}
|
||||||
|
SPDLOG_LOGGER_CATCH()
|
||||||
|
}
|
||||||
|
|
||||||
|
// T can be statically converted to wstring_view, and no formatting needed.
|
||||||
|
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::wstring_view_t>::value, int>::type = 0>
|
||||||
|
void log_(source_loc loc, level::level_enum lvl, const T &msg)
|
||||||
|
{
|
||||||
|
bool log_enabled = should_log(lvl);
|
||||||
|
bool traceback_enabled = tracer_.enabled();
|
||||||
|
if (!log_enabled && !traceback_enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SPDLOG_TRY
|
||||||
|
{
|
||||||
|
memory_buf_t buf;
|
||||||
|
details::os::wstr_to_utf8buf(msg, buf);
|
||||||
|
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
|
||||||
|
log_it_(log_msg, log_enabled, traceback_enabled);
|
||||||
|
}
|
||||||
|
SPDLOG_LOGGER_CATCH()
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
|
|
||||||
// log the given message (if the given log level is high enough),
|
// log the given message (if the given log level is high enough),
|
||||||
// and save backtrace (if backtrace is enabled).
|
// and save backtrace (if backtrace is enabled).
|
||||||
void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled);
|
void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled);
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
#include <spdlog/fmt/fmt.h>
|
#include <spdlog/fmt/fmt.h>
|
||||||
#include <spdlog/formatter.h>
|
#include <spdlog/formatter.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -45,12 +47,12 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (padinfo_.side_ == padding_info::left)
|
if (padinfo_.side_ == padding_info::pad_side::left)
|
||||||
{
|
{
|
||||||
pad_it(remaining_pad_);
|
pad_it(remaining_pad_);
|
||||||
remaining_pad_ = 0;
|
remaining_pad_ = 0;
|
||||||
}
|
}
|
||||||
else if (padinfo_.side_ == padding_info::center)
|
else if (padinfo_.side_ == padding_info::pad_side::center)
|
||||||
{
|
{
|
||||||
auto half_pad = remaining_pad_ / 2;
|
auto half_pad = remaining_pad_ / 2;
|
||||||
auto reminder = remaining_pad_ & 1;
|
auto reminder = remaining_pad_ & 1;
|
||||||
@@ -127,7 +129,7 @@ public:
|
|||||||
|
|
||||||
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
|
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
|
||||||
{
|
{
|
||||||
string_view_t &level_name = level::to_string_view(msg.level);
|
const string_view_t &level_name = level::to_string_view(msg.level);
|
||||||
ScopedPadder p(level_name.size(), padinfo_, dest);
|
ScopedPadder p(level_name.size(), padinfo_, dest);
|
||||||
fmt_helper::append_string_view(level_name, dest);
|
fmt_helper::append_string_view(level_name, dest);
|
||||||
}
|
}
|
||||||
@@ -814,11 +816,31 @@ public:
|
|||||||
: flag_formatter(padinfo)
|
: flag_formatter(padinfo)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# pragma warning(push)
|
||||||
|
# pragma warning(disable : 4127) // consider using 'if constexpr' instead
|
||||||
|
#endif // _MSC_VER
|
||||||
static const char *basename(const char *filename)
|
static const char *basename(const char *filename)
|
||||||
{
|
{
|
||||||
const char *rv = std::strrchr(filename, os::folder_sep);
|
// if the size is 2 (1 character + null terminator) we can use the more efficient strrchr
|
||||||
|
// the branch will be elided by optimizations
|
||||||
|
if (sizeof(os::folder_seps) == 2)
|
||||||
|
{
|
||||||
|
const char *rv = std::strrchr(filename, os::folder_seps[0]);
|
||||||
return rv != nullptr ? rv + 1 : filename;
|
return rv != nullptr ? rv + 1 : filename;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const std::reverse_iterator<const char *> begin(filename + std::strlen(filename));
|
||||||
|
const std::reverse_iterator<const char *> end(filename);
|
||||||
|
|
||||||
|
const auto it = std::find_first_of(begin, end, std::begin(os::folder_seps), std::end(os::folder_seps) - 1);
|
||||||
|
return it != end ? it.base() : filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# pragma warning(pop)
|
||||||
|
#endif // _MSC_VER
|
||||||
|
|
||||||
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
|
void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
|
||||||
{
|
{
|
||||||
@@ -903,7 +925,7 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Full info formatter
|
// Full info formatter
|
||||||
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v
|
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
|
||||||
class full_formatter final : public flag_formatter
|
class full_formatter final : public flag_formatter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -1246,9 +1268,24 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i
|
|||||||
|
|
||||||
default: // Unknown flag appears as is
|
default: // Unknown flag appears as is
|
||||||
auto unknown_flag = details::make_unique<details::aggregate_formatter>();
|
auto unknown_flag = details::make_unique<details::aggregate_formatter>();
|
||||||
|
|
||||||
|
if (!padding.truncate_)
|
||||||
|
{
|
||||||
unknown_flag->add_ch('%');
|
unknown_flag->add_ch('%');
|
||||||
unknown_flag->add_ch(flag);
|
unknown_flag->add_ch(flag);
|
||||||
formatters_.push_back((std::move(unknown_flag)));
|
formatters_.push_back((std::move(unknown_flag)));
|
||||||
|
}
|
||||||
|
// fix issue #1617 (prev char was '!' and should have been treated as funcname flag instead of truncating flag)
|
||||||
|
// spdlog::set_pattern("[%10!] %v") => "[ main] some message"
|
||||||
|
// spdlog::set_pattern("[%3!!] %v") => "[mai] some message"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
padding.truncate_ = false;
|
||||||
|
formatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));
|
||||||
|
unknown_flag->add_ch(flag);
|
||||||
|
formatters_.push_back((std::move(unknown_flag)));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1270,15 +1307,15 @@ SPDLOG_INLINE details::padding_info pattern_formatter::handle_padspec_(std::stri
|
|||||||
switch (*it)
|
switch (*it)
|
||||||
{
|
{
|
||||||
case '-':
|
case '-':
|
||||||
side = padding_info::right;
|
side = padding_info::pad_side::right;
|
||||||
++it;
|
++it;
|
||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
side = padding_info::center;
|
side = padding_info::pad_side::center;
|
||||||
++it;
|
++it;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
side = details::padding_info::left;
|
side = details::padding_info::pad_side::left;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace details {
|
|||||||
// padding information.
|
// padding information.
|
||||||
struct padding_info
|
struct padding_info
|
||||||
{
|
{
|
||||||
enum pad_side
|
enum class pad_side
|
||||||
{
|
{
|
||||||
left,
|
left,
|
||||||
right,
|
right,
|
||||||
@@ -42,7 +42,7 @@ struct padding_info
|
|||||||
return enabled_;
|
return enabled_;
|
||||||
}
|
}
|
||||||
size_t width_ = 0;
|
size_t width_ = 0;
|
||||||
pad_side side_ = left;
|
pad_side side_ = pad_side::left;
|
||||||
bool truncate_ = false;
|
bool truncate_ = false;
|
||||||
bool enabled_ = false;
|
bool enabled_ = false;
|
||||||
};
|
};
|
||||||
@@ -92,9 +92,9 @@ public:
|
|||||||
void format(const details::log_msg &msg, memory_buf_t &dest) override;
|
void format(const details::log_msg &msg, memory_buf_t &dest) override;
|
||||||
|
|
||||||
template<typename T, typename... Args>
|
template<typename T, typename... Args>
|
||||||
pattern_formatter &add_flag(char flag, const Args &... args)
|
pattern_formatter &add_flag(char flag, Args &&...args)
|
||||||
{
|
{
|
||||||
custom_handlers_[flag] = details::make_unique<T>(args...);
|
custom_handlers_[flag] = details::make_unique<T>(std::forward<Args>(args)...);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
void set_pattern(std::string pattern);
|
void set_pattern(std::string pattern);
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
|
|||||||
case color_mode::never:
|
case color_mode::never:
|
||||||
should_do_colors_ = false;
|
should_do_colors_ = false;
|
||||||
return;
|
return;
|
||||||
|
default:
|
||||||
|
should_do_colors_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
// sink formatter
|
// sink formatter
|
||||||
std::unique_ptr<spdlog::formatter> formatter_;
|
std::unique_ptr<spdlog::formatter> formatter_;
|
||||||
Mutex mutex_;
|
mutable Mutex mutex_;
|
||||||
|
|
||||||
virtual void sink_it_(const details::log_msg &msg) = 0;
|
virtual void sink_it_(const details::log_msg &msg) = 0;
|
||||||
virtual void flush_() = 0;
|
virtual void flush_() = 0;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <spdlog/details/file_helper.h>
|
#include <spdlog/details/file_helper.h>
|
||||||
#include <spdlog/details/null_mutex.h>
|
#include <spdlog/details/null_mutex.h>
|
||||||
#include <spdlog/fmt/fmt.h>
|
#include <spdlog/fmt/fmt.h>
|
||||||
|
#include <spdlog/fmt/chrono.h>
|
||||||
#include <spdlog/sinks/base_sink.h>
|
#include <spdlog/sinks/base_sink.h>
|
||||||
#include <spdlog/details/os.h>
|
#include <spdlog/details/os.h>
|
||||||
#include <spdlog/details/circular_q.h>
|
#include <spdlog/details/circular_q.h>
|
||||||
@@ -36,6 +37,27 @@ struct daily_filename_calculator
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generator of daily log file names with strftime format.
|
||||||
|
* Usages:
|
||||||
|
* auto sink = std::make_shared<spdlog::sinks::daily_file_format_sink_mt>("myapp-%Y-%m-%d:%H:%M:%S.log", hour, minute);"
|
||||||
|
* auto logger = spdlog::daily_logger_format_mt("loggername, "myapp-%Y-%m-%d:%X.log", hour, minute)"
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct daily_filename_format_calculator
|
||||||
|
{
|
||||||
|
static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
|
||||||
|
{
|
||||||
|
// generate fmt datetime format string, e.g. {:%Y-%m-%d}.
|
||||||
|
filename_t fmt_filename = fmt::format(SPDLOG_FILENAME_T("{{:{}}}"), filename);
|
||||||
|
#if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) // for some reason msvc doesnt allow fmt::runtime(..) with wchar here
|
||||||
|
return fmt::format(fmt_filename, now_tm);
|
||||||
|
#else
|
||||||
|
return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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.
|
||||||
@@ -182,6 +204,8 @@ private:
|
|||||||
|
|
||||||
using daily_file_sink_mt = daily_file_sink<std::mutex>;
|
using daily_file_sink_mt = daily_file_sink<std::mutex>;
|
||||||
using daily_file_sink_st = daily_file_sink<details::null_mutex>;
|
using daily_file_sink_st = daily_file_sink<details::null_mutex>;
|
||||||
|
using daily_file_format_sink_mt = daily_file_sink<std::mutex, daily_filename_format_calculator>;
|
||||||
|
using daily_file_format_sink_st = daily_file_sink<details::null_mutex, daily_filename_format_calculator>;
|
||||||
|
|
||||||
} // namespace sinks
|
} // namespace sinks
|
||||||
|
|
||||||
@@ -195,10 +219,24 @@ inline std::shared_ptr<logger> daily_logger_mt(
|
|||||||
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
|
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> daily_logger_format_mt(
|
||||||
|
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::daily_file_format_sink_mt>(logger_name, filename, hour, minute, truncate, max_files);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Factory = spdlog::synchronous_factory>
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
inline std::shared_ptr<logger> daily_logger_st(
|
inline std::shared_ptr<logger> daily_logger_st(
|
||||||
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
|
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
|
||||||
{
|
{
|
||||||
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
|
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> daily_logger_format_st(
|
||||||
|
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::daily_file_format_sink_st>(logger_name, filename, hour, minute, truncate, max_files);
|
||||||
|
}
|
||||||
} // namespace spdlog
|
} // namespace spdlog
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <spdlog/details/null_mutex.h>
|
#include <spdlog/details/null_mutex.h>
|
||||||
#include <spdlog/details/log_msg.h>
|
#include <spdlog/details/log_msg.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -62,11 +63,14 @@ protected:
|
|||||||
// log the "skipped.." message
|
// log the "skipped.." message
|
||||||
if (skip_counter_ > 0)
|
if (skip_counter_ > 0)
|
||||||
{
|
{
|
||||||
memory_buf_t buf;
|
char buf[64];
|
||||||
fmt::format_to(buf, "Skipped {} duplicate messages..", skip_counter_);
|
auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", static_cast<unsigned>(skip_counter_));
|
||||||
details::log_msg skipped_msg{msg.logger_name, msg.level, string_view_t{buf.data(), buf.size()}};
|
if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf))
|
||||||
|
{
|
||||||
|
details::log_msg skipped_msg{msg.logger_name, level::info, string_view_t{buf, static_cast<size_t>(msg_size)}};
|
||||||
dist_sink<Mutex>::sink_it_(skipped_msg);
|
dist_sink<Mutex>::sink_it_(skipped_msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// log current message
|
// log current message
|
||||||
dist_sink<Mutex>::sink_it_(msg);
|
dist_sink<Mutex>::sink_it_(msg);
|
||||||
|
|||||||
194
include/spdlog/sinks/hourly_file_sink.h
Normal file
194
include/spdlog/sinks/hourly_file_sink.h
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <spdlog/common.h>
|
||||||
|
#include <spdlog/details/file_helper.h>
|
||||||
|
#include <spdlog/details/null_mutex.h>
|
||||||
|
#include <spdlog/fmt/fmt.h>
|
||||||
|
#include <spdlog/sinks/base_sink.h>
|
||||||
|
#include <spdlog/details/os.h>
|
||||||
|
#include <spdlog/details/circular_q.h>
|
||||||
|
#include <spdlog/details/synchronous_factory.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <ctime>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace spdlog {
|
||||||
|
namespace sinks {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generator of Hourly log file names in format basename.YYYY-MM-DD-HH.ext
|
||||||
|
*/
|
||||||
|
struct hourly_filename_calculator
|
||||||
|
{
|
||||||
|
// Create filename for the form basename.YYYY-MM-DD-H
|
||||||
|
static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
|
||||||
|
{
|
||||||
|
filename_t basename, ext;
|
||||||
|
std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
|
||||||
|
return fmt::format(SPDLOG_FILENAME_T("{}_{:04d}{:02d}{:02d}_{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
|
||||||
|
now_tm.tm_mday, now_tm.tm_hour, ext);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rotating file sink based on time.
|
||||||
|
* If truncate != false , the created file will be truncated.
|
||||||
|
* If max_files > 0, retain only the last max_files and delete previous.
|
||||||
|
*/
|
||||||
|
template<typename Mutex, typename FileNameCalc = hourly_filename_calculator>
|
||||||
|
class hourly_file_sink final : public base_sink<Mutex>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// create hourly file sink which rotates on given time
|
||||||
|
hourly_file_sink(filename_t base_filename, bool truncate = false, uint16_t max_files = 0)
|
||||||
|
: base_filename_(std::move(base_filename))
|
||||||
|
, truncate_(truncate)
|
||||||
|
, max_files_(max_files)
|
||||||
|
, filenames_q_()
|
||||||
|
{
|
||||||
|
auto now = log_clock::now();
|
||||||
|
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
|
||||||
|
file_helper_.open(filename, truncate_);
|
||||||
|
rotation_tp_ = next_rotation_tp_();
|
||||||
|
|
||||||
|
if (max_files_ > 0)
|
||||||
|
{
|
||||||
|
init_filenames_q_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filename_t filename()
|
||||||
|
{
|
||||||
|
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
|
||||||
|
return file_helper_.filename();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sink_it_(const details::log_msg &msg) override
|
||||||
|
{
|
||||||
|
auto time = msg.time;
|
||||||
|
bool should_rotate = time >= rotation_tp_;
|
||||||
|
if (should_rotate)
|
||||||
|
{
|
||||||
|
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time));
|
||||||
|
file_helper_.open(filename, truncate_);
|
||||||
|
rotation_tp_ = next_rotation_tp_();
|
||||||
|
}
|
||||||
|
memory_buf_t formatted;
|
||||||
|
base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
|
file_helper_.write(formatted);
|
||||||
|
|
||||||
|
// Do the cleaning only at the end because it might throw on failure.
|
||||||
|
if (should_rotate && max_files_ > 0)
|
||||||
|
{
|
||||||
|
delete_old_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_() override
|
||||||
|
{
|
||||||
|
file_helper_.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void init_filenames_q_()
|
||||||
|
{
|
||||||
|
using details::os::path_exists;
|
||||||
|
|
||||||
|
filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_));
|
||||||
|
std::vector<filename_t> filenames;
|
||||||
|
auto now = log_clock::now();
|
||||||
|
while (filenames.size() < max_files_)
|
||||||
|
{
|
||||||
|
auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
|
||||||
|
if (!path_exists(filename))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
filenames.emplace_back(filename);
|
||||||
|
now -= std::chrono::hours(1);
|
||||||
|
}
|
||||||
|
for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter)
|
||||||
|
{
|
||||||
|
filenames_q_.push_back(std::move(*iter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tm now_tm(log_clock::time_point tp)
|
||||||
|
{
|
||||||
|
time_t tnow = log_clock::to_time_t(tp);
|
||||||
|
return spdlog::details::os::localtime(tnow);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_clock::time_point next_rotation_tp_()
|
||||||
|
{
|
||||||
|
auto now = log_clock::now();
|
||||||
|
tm date = now_tm(now);
|
||||||
|
date.tm_min = 0;
|
||||||
|
date.tm_sec = 0;
|
||||||
|
auto rotation_time = log_clock::from_time_t(std::mktime(&date));
|
||||||
|
if (rotation_time > now)
|
||||||
|
{
|
||||||
|
return rotation_time;
|
||||||
|
}
|
||||||
|
return {rotation_time + std::chrono::hours(1)};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the file N rotations ago.
|
||||||
|
// Throw spdlog_ex on failure to delete the old file.
|
||||||
|
void delete_old_()
|
||||||
|
{
|
||||||
|
using details::os::filename_to_str;
|
||||||
|
using details::os::remove_if_exists;
|
||||||
|
|
||||||
|
filename_t current_file = file_helper_.filename();
|
||||||
|
if (filenames_q_.full())
|
||||||
|
{
|
||||||
|
auto old_filename = std::move(filenames_q_.front());
|
||||||
|
filenames_q_.pop_front();
|
||||||
|
bool ok = remove_if_exists(old_filename) == 0;
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
filenames_q_.push_back(std::move(current_file));
|
||||||
|
SPDLOG_THROW(spdlog_ex("Failed removing hourly file " + filename_to_str(old_filename), errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filenames_q_.push_back(std::move(current_file));
|
||||||
|
}
|
||||||
|
|
||||||
|
filename_t base_filename_;
|
||||||
|
log_clock::time_point rotation_tp_;
|
||||||
|
details::file_helper file_helper_;
|
||||||
|
bool truncate_;
|
||||||
|
uint16_t max_files_;
|
||||||
|
details::circular_q<filename_t> filenames_q_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using hourly_file_sink_mt = hourly_file_sink<std::mutex>;
|
||||||
|
using hourly_file_sink_st = hourly_file_sink<details::null_mutex>;
|
||||||
|
|
||||||
|
} // namespace sinks
|
||||||
|
|
||||||
|
//
|
||||||
|
// factory functions
|
||||||
|
//
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> hourly_logger_mt(
|
||||||
|
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> hourly_logger_st(
|
||||||
|
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files);
|
||||||
|
}
|
||||||
|
} // namespace spdlog
|
||||||
98
include/spdlog/sinks/mongo_sink.h
Normal file
98
include/spdlog/sinks/mongo_sink.h
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom sink for mongodb
|
||||||
|
// Building and using requires mongocxx library.
|
||||||
|
// For building mongocxx library check the url below
|
||||||
|
// http://mongocxx.org/mongocxx-v3/installation/
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "spdlog/common.h"
|
||||||
|
#include "spdlog/details/log_msg.h"
|
||||||
|
#include "spdlog/sinks/base_sink.h"
|
||||||
|
#include <spdlog/details/synchronous_factory.h>
|
||||||
|
|
||||||
|
#include <bsoncxx/builder/stream/document.hpp>
|
||||||
|
#include <bsoncxx/types.hpp>
|
||||||
|
#include <bsoncxx/view_or_value.hpp>
|
||||||
|
|
||||||
|
#include <mongocxx/client.hpp>
|
||||||
|
#include <mongocxx/instance.hpp>
|
||||||
|
#include <mongocxx/uri.hpp>
|
||||||
|
|
||||||
|
namespace spdlog {
|
||||||
|
namespace sinks {
|
||||||
|
template<typename Mutex>
|
||||||
|
class mongo_sink : public base_sink<Mutex>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
mongo_sink(const std::string &db_name, const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client_ = std::make_unique<mongocxx::client>(mongocxx::uri{uri});
|
||||||
|
db_name_ = db_name;
|
||||||
|
coll_name_ = collection_name;
|
||||||
|
}
|
||||||
|
catch (const std::exception)
|
||||||
|
{
|
||||||
|
throw spdlog_ex("Error opening database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~mongo_sink()
|
||||||
|
{
|
||||||
|
flush_();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sink_it_(const details::log_msg &msg) override
|
||||||
|
{
|
||||||
|
using bsoncxx::builder::stream::document;
|
||||||
|
using bsoncxx::builder::stream::finalize;
|
||||||
|
|
||||||
|
if (client_ != nullptr)
|
||||||
|
{
|
||||||
|
auto doc = document{} << "timestamp" << bsoncxx::types::b_date(msg.time) << "level" << level::to_string_view(msg.level).data()
|
||||||
|
<< "message" << std::string(msg.payload.begin(), msg.payload.end()) << "logger_name"
|
||||||
|
<< std::string(msg.logger_name.begin(), msg.logger_name.end()) << "thread_id"
|
||||||
|
<< static_cast<int>(msg.thread_id) << finalize;
|
||||||
|
client_->database(db_name_).collection(coll_name_).insert_one(doc.view());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static mongocxx::instance instance_;
|
||||||
|
std::string db_name_;
|
||||||
|
std::string coll_name_;
|
||||||
|
std::unique_ptr<mongocxx::client> client_ = nullptr;
|
||||||
|
};
|
||||||
|
mongocxx::instance mongo_sink<std::mutex>::instance_{};
|
||||||
|
|
||||||
|
#include "spdlog/details/null_mutex.h"
|
||||||
|
#include <mutex>
|
||||||
|
using mongo_sink_mt = mongo_sink<std::mutex>;
|
||||||
|
using mongo_sink_st = mongo_sink<spdlog::details::null_mutex>;
|
||||||
|
|
||||||
|
} // namespace sinks
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> mongo_logger_mt(const std::string &logger_name, const std::string &db_name,
|
||||||
|
const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::mongo_sink_mt>(logger_name, db_name, collection_name, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> mongo_logger_st(const std::string &logger_name, const std::string &db_name,
|
||||||
|
const std::string &collection_name, const std::string &uri = "mongodb://localhost:27017")
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::mongo_sink_st>(logger_name, db_name, collection_name, uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace spdlog
|
||||||
@@ -8,12 +8,12 @@
|
|||||||
# include <spdlog/details/null_mutex.h>
|
# include <spdlog/details/null_mutex.h>
|
||||||
# include <spdlog/sinks/base_sink.h>
|
# include <spdlog/sinks/base_sink.h>
|
||||||
|
|
||||||
#include <spdlog/details/windows_include.h>
|
|
||||||
#include <winbase.h>
|
|
||||||
|
|
||||||
# include <mutex>
|
# include <mutex>
|
||||||
# include <string>
|
# include <string>
|
||||||
|
|
||||||
|
// Avoid including windows.h (https://stackoverflow.com/a/30741042)
|
||||||
|
extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char *lpOutputString);
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace sinks {
|
namespace sinks {
|
||||||
/*
|
/*
|
||||||
@@ -23,12 +23,11 @@ template<typename Mutex>
|
|||||||
class msvc_sink : public base_sink<Mutex>
|
class msvc_sink : public base_sink<Mutex>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit msvc_sink() {}
|
msvc_sink() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void sink_it_(const details::log_msg &msg) override
|
void sink_it_(const details::log_msg &msg) override
|
||||||
{
|
{
|
||||||
|
|
||||||
memory_buf_t formatted;
|
memory_buf_t formatted;
|
||||||
base_sink<Mutex>::formatter_->format(msg, formatted);
|
base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
OutputDebugStringA(fmt::to_string(formatted).c_str());
|
OutputDebugStringA(fmt::to_string(formatted).c_str());
|
||||||
|
|||||||
200
include/spdlog/sinks/qt_sinks.h
Normal file
200
include/spdlog/sinks/qt_sinks.h
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
// Copyright(c) 2015-present, Gabi Melman, mguludag and spdlog contributors.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//
|
||||||
|
// Custom sink for QPlainTextEdit or QTextEdit and its children(QTextBrowser...
|
||||||
|
// etc) Building and using requires Qt library.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "spdlog/common.h"
|
||||||
|
#include "spdlog/details/log_msg.h"
|
||||||
|
#include "spdlog/details/synchronous_factory.h"
|
||||||
|
#include "spdlog/sinks/base_sink.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QTextEdit>
|
||||||
|
|
||||||
|
namespace _spdlog_p {
|
||||||
|
namespace _sinks_p {
|
||||||
|
//
|
||||||
|
// Private class for QTextEdit and its derivatives
|
||||||
|
//
|
||||||
|
class qtextedit_sink_p : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
qtextedit_sink_p(QTextEdit *textedit = nullptr)
|
||||||
|
{
|
||||||
|
if (textedit != nullptr)
|
||||||
|
{
|
||||||
|
textedit_ = textedit;
|
||||||
|
connect(this, &qtextedit_sink_p::append_text, textedit_, &QTextEdit::append);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~qtextedit_sink_p() {}
|
||||||
|
|
||||||
|
void append(const spdlog::string_view_t &str)
|
||||||
|
{
|
||||||
|
emit append_text(QString::fromUtf8(str.data(), static_cast<int>(str.size() - 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void append_text(const QString &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTextEdit *textedit_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private class for QPlainTextEdit
|
||||||
|
//
|
||||||
|
class qplaintextedit_sink_p : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
qplaintextedit_sink_p(QPlainTextEdit *textedit = nullptr)
|
||||||
|
{
|
||||||
|
if (textedit != nullptr)
|
||||||
|
{
|
||||||
|
textedit_ = textedit;
|
||||||
|
connect(this, &qplaintextedit_sink_p::append_text, textedit_, &QPlainTextEdit::appendPlainText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~qplaintextedit_sink_p() {}
|
||||||
|
|
||||||
|
void append(const spdlog::string_view_t &str)
|
||||||
|
{
|
||||||
|
emit append_text(QString::fromUtf8(str.data(), static_cast<int>(str.size() - 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void append_text(const QString &);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPlainTextEdit *textedit_ = nullptr;
|
||||||
|
};
|
||||||
|
} // namespace _sinks_p
|
||||||
|
} // namespace _spdlog_p
|
||||||
|
|
||||||
|
//
|
||||||
|
// qtextedit_sink class
|
||||||
|
//
|
||||||
|
namespace spdlog {
|
||||||
|
namespace sinks {
|
||||||
|
template<typename Mutex>
|
||||||
|
class qtextedit_sink : public base_sink<Mutex>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
qtextedit_sink(QTextEdit *textedit = nullptr)
|
||||||
|
{
|
||||||
|
if (textedit != nullptr)
|
||||||
|
{
|
||||||
|
textedit_p = std::make_shared<_spdlog_p::_sinks_p::qtextedit_sink_p>(textedit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw spdlog_ex("Error opening QTextEdit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~qtextedit_sink()
|
||||||
|
{
|
||||||
|
flush_();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sink_it_(const details::log_msg &msg) override
|
||||||
|
{
|
||||||
|
memory_buf_t formatted;
|
||||||
|
base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
|
string_view_t str_v = string_view_t(formatted.data(), formatted.size());
|
||||||
|
textedit_p->append(str_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<_spdlog_p::_sinks_p::qtextedit_sink_p> textedit_p = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// qplaintextedit_sink class
|
||||||
|
//
|
||||||
|
template<typename Mutex>
|
||||||
|
class qplaintextedit_sink : public base_sink<Mutex>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
qplaintextedit_sink(QPlainTextEdit *textedit = nullptr)
|
||||||
|
{
|
||||||
|
if (textedit != nullptr)
|
||||||
|
{
|
||||||
|
textedit_p = std::make_shared<_spdlog_p::_sinks_p::qplaintextedit_sink_p>(textedit);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw spdlog_ex("Error opening QPlainTextEdit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~qplaintextedit_sink()
|
||||||
|
{
|
||||||
|
flush_();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void sink_it_(const details::log_msg &msg) override
|
||||||
|
{
|
||||||
|
memory_buf_t formatted;
|
||||||
|
base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
|
string_view_t str_v = string_view_t(formatted.data(), formatted.size());
|
||||||
|
textedit_p->append(str_v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush_() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<_spdlog_p::_sinks_p::qplaintextedit_sink_p> textedit_p = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "spdlog/details/null_mutex.h"
|
||||||
|
#include <mutex>
|
||||||
|
using qtextedit_sink_mt = qtextedit_sink<std::mutex>;
|
||||||
|
using qtextedit_sink_st = qtextedit_sink<spdlog::details::null_mutex>;
|
||||||
|
|
||||||
|
using qplaintextedit_sink_mt = qplaintextedit_sink<std::mutex>;
|
||||||
|
using qplaintextedit_sink_st = qplaintextedit_sink<spdlog::details::null_mutex>;
|
||||||
|
|
||||||
|
} // namespace sinks
|
||||||
|
|
||||||
|
//
|
||||||
|
// Factory functions
|
||||||
|
//
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> qtextedit_logger_mt(const std::string &logger_name, QTextEdit *qtextedit = nullptr)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::qtextedit_sink_mt>(logger_name, qtextedit);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> qtextedit_logger_st(const std::string &logger_name, QTextEdit *qtextedit = nullptr)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::qtextedit_sink_st>(logger_name, qtextedit);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> qplaintextedit_logger_mt(const std::string &logger_name, QPlainTextEdit *qplaintextedit = nullptr)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::qplaintextedit_sink_mt>(logger_name, qplaintextedit);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Factory = spdlog::synchronous_factory>
|
||||||
|
inline std::shared_ptr<logger> qplaintextedit_logger_st(const std::string &logger_name, QPlainTextEdit *qplaintextedit = nullptr)
|
||||||
|
{
|
||||||
|
return Factory::template create<sinks::qplaintextedit_sink_st>(logger_name, qplaintextedit);
|
||||||
|
}
|
||||||
|
} // namespace spdlog
|
||||||
@@ -11,6 +11,19 @@
|
|||||||
#include <spdlog/pattern_formatter.h>
|
#include <spdlog/pattern_formatter.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675)
|
||||||
|
// so instead we use ::FileWrite
|
||||||
|
# include <spdlog/details/windows_include.h>
|
||||||
|
|
||||||
|
# ifndef _USING_V110_SDK71_ // fileapi.h doesnt exist in winxp
|
||||||
|
# include <fileapi.h> // WriteFile (..)
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include <io.h> // _get_osfhandle(..)
|
||||||
|
# include <stdio.h> // _fileno(..)
|
||||||
|
#endif // WIN32
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
|
|
||||||
namespace sinks {
|
namespace sinks {
|
||||||
@@ -20,16 +33,48 @@ SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file)
|
|||||||
: mutex_(ConsoleMutex::mutex())
|
: mutex_(ConsoleMutex::mutex())
|
||||||
, file_(file)
|
, file_(file)
|
||||||
, formatter_(details::make_unique<spdlog::pattern_formatter>())
|
, formatter_(details::make_unique<spdlog::pattern_formatter>())
|
||||||
{}
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
// get windows handle from the FILE* object
|
||||||
|
|
||||||
|
handle_ = (HANDLE)::_get_osfhandle(::_fileno(file_));
|
||||||
|
|
||||||
|
// don't throw to support cases where no console is attached,
|
||||||
|
// and let the log method to do nothing if (handle_ == INVALID_HANDLE_VALUE).
|
||||||
|
// throw only if non stdout/stderr target is requested (probably regular file and not console).
|
||||||
|
if (handle_ == INVALID_HANDLE_VALUE && file != stdout && file != stderr)
|
||||||
|
{
|
||||||
|
throw_spdlog_ex("spdlog::stdout_sink_base: _get_osfhandle() failed", errno);
|
||||||
|
}
|
||||||
|
#endif // WIN32
|
||||||
|
}
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg)
|
SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg)
|
||||||
{
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (handle_ == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
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_);
|
::fflush(file_); // flush in case there is somthing in this file_ already
|
||||||
fflush(file_); // flush every line to terminal
|
auto size = static_cast<DWORD>(formatted.size());
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0;
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
throw_spdlog_ex("stdout_sink_base: WriteFile() failed. GetLastError(): " + std::to_string(::GetLastError()));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
std::lock_guard<mutex_t> lock(mutex_);
|
||||||
|
memory_buf_t formatted;
|
||||||
|
formatter_->format(msg, formatted);
|
||||||
|
::fwrite(formatted.data(), sizeof(char), formatted.size(), file_);
|
||||||
|
::fflush(file_); // flush every line to terminal
|
||||||
|
#endif // WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
#include <spdlog/sinks/sink.h>
|
#include <spdlog/sinks/sink.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <spdlog/details/windows_include.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
|
|
||||||
namespace sinks {
|
namespace sinks {
|
||||||
@@ -36,6 +40,9 @@ protected:
|
|||||||
mutex_t &mutex_;
|
mutex_t &mutex_;
|
||||||
FILE *file_;
|
FILE *file_;
|
||||||
std::unique_ptr<spdlog::formatter> formatter_;
|
std::unique_ptr<spdlog::formatter> formatter_;
|
||||||
|
#ifdef _WIN32
|
||||||
|
HANDLE handle_;
|
||||||
|
#endif // WIN32
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ struct win32_error : public spdlog_ex
|
|||||||
|
|
||||||
LPSTR format_message_result{};
|
LPSTR format_message_result{};
|
||||||
auto format_message_succeeded =
|
auto format_message_succeeded =
|
||||||
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
|
::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
|
||||||
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result, 0, nullptr);
|
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result, 0, nullptr);
|
||||||
|
|
||||||
if (format_message_succeeded && format_message_result)
|
if (format_message_succeeded && format_message_result)
|
||||||
@@ -203,7 +203,7 @@ private:
|
|||||||
{
|
{
|
||||||
if (!hEventLog_)
|
if (!hEventLog_)
|
||||||
{
|
{
|
||||||
hEventLog_ = ::RegisterEventSource(nullptr, source_.c_str());
|
hEventLog_ = ::RegisterEventSourceA(nullptr, source_.c_str());
|
||||||
if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED)
|
if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED)
|
||||||
{
|
{
|
||||||
SPDLOG_THROW(internal::win32_error("RegisterEventSource"));
|
SPDLOG_THROW(internal::win32_error("RegisterEventSource"));
|
||||||
@@ -218,13 +218,23 @@ protected:
|
|||||||
{
|
{
|
||||||
using namespace internal;
|
using namespace internal;
|
||||||
|
|
||||||
|
bool succeeded;
|
||||||
memory_buf_t formatted;
|
memory_buf_t formatted;
|
||||||
base_sink<Mutex>::formatter_->format(msg, formatted);
|
base_sink<Mutex>::formatter_->format(msg, formatted);
|
||||||
formatted.push_back('\0');
|
formatted.push_back('\0');
|
||||||
LPCSTR lp_str = static_cast<LPCSTR>(formatted.data());
|
|
||||||
|
|
||||||
auto succeeded = ::ReportEvent(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_,
|
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
|
||||||
|
wmemory_buf_t buf;
|
||||||
|
details::os::utf8_to_wstrbuf(string_view_t(formatted.data(), formatted.size()), buf);
|
||||||
|
|
||||||
|
LPCWSTR lp_wstr = buf.data();
|
||||||
|
succeeded = ::ReportEventW(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_,
|
||||||
|
current_user_sid_.as_sid(), 1, 0, &lp_wstr, nullptr);
|
||||||
|
#else
|
||||||
|
LPCSTR lp_str = formatted.data();
|
||||||
|
succeeded = ::ReportEventA(event_log_handle(), eventlog::get_event_type(msg), eventlog::get_event_category(msg), event_id_,
|
||||||
current_user_sid_.as_sid(), 1, 0, &lp_str, nullptr);
|
current_user_sid_.as_sid(), 1, 0, &lp_str, nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!succeeded)
|
if (!succeeded)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,30 +7,30 @@
|
|||||||
# include <spdlog/sinks/wincolor_sink.h>
|
# include <spdlog/sinks/wincolor_sink.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <spdlog/details/windows_include.h>
|
||||||
|
#include <wincon.h>
|
||||||
|
|
||||||
#include <spdlog/common.h>
|
#include <spdlog/common.h>
|
||||||
#include <spdlog/pattern_formatter.h>
|
#include <spdlog/pattern_formatter.h>
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace sinks {
|
namespace sinks {
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(HANDLE out_handle, color_mode mode)
|
SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(void *out_handle, color_mode mode)
|
||||||
: out_handle_(out_handle)
|
: out_handle_(out_handle)
|
||||||
, mutex_(ConsoleMutex::mutex())
|
, mutex_(ConsoleMutex::mutex())
|
||||||
, formatter_(details::make_unique<spdlog::pattern_formatter>())
|
, formatter_(details::make_unique<spdlog::pattern_formatter>())
|
||||||
{
|
{
|
||||||
// check if out_handle is points to the actual console.
|
|
||||||
// ::GetConsoleMode() should return 0 if it is redirected or not valid console handle.
|
|
||||||
DWORD console_mode;
|
|
||||||
in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0;
|
|
||||||
|
|
||||||
set_color_mode(mode);
|
set_color_mode_impl(mode);
|
||||||
colors_[level::trace] = WHITE;
|
// set level colors
|
||||||
colors_[level::debug] = CYAN;
|
colors_[level::trace] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; // white
|
||||||
colors_[level::info] = GREEN;
|
colors_[level::debug] = FOREGROUND_GREEN | FOREGROUND_BLUE; // cyan
|
||||||
colors_[level::warn] = YELLOW | BOLD;
|
colors_[level::info] = FOREGROUND_GREEN; // green
|
||||||
colors_[level::err] = RED | BOLD; // red bold
|
colors_[level::warn] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; // intense yellow
|
||||||
colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background
|
colors_[level::err] = FOREGROUND_RED | FOREGROUND_INTENSITY; // intense red
|
||||||
|
colors_[level::critical] =
|
||||||
|
BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY; // intense white on red background
|
||||||
colors_[level::off] = 0;
|
colors_[level::off] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ SPDLOG_INLINE wincolor_sink<ConsoleMutex>::~wincolor_sink()
|
|||||||
|
|
||||||
// change the color for the given level
|
// change the color for the given level
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, WORD color)
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, std::uint16_t color)
|
||||||
{
|
{
|
||||||
std::lock_guard<mutex_t> lock(mutex_);
|
std::lock_guard<mutex_t> lock(mutex_);
|
||||||
colors_[level] = color;
|
colors_[level] = color;
|
||||||
@@ -51,31 +51,30 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum leve
|
|||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg)
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg)
|
||||||
{
|
{
|
||||||
|
if (out_handle_ == nullptr || out_handle_ == INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::lock_guard<mutex_t> lock(mutex_);
|
std::lock_guard<mutex_t> lock(mutex_);
|
||||||
msg.color_range_start = 0;
|
msg.color_range_start = 0;
|
||||||
msg.color_range_end = 0;
|
msg.color_range_end = 0;
|
||||||
memory_buf_t formatted;
|
memory_buf_t formatted;
|
||||||
formatter_->format(msg, formatted);
|
formatter_->format(msg, formatted);
|
||||||
if (!in_console_)
|
|
||||||
{
|
|
||||||
write_to_file_(formatted);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (should_do_colors_ && msg.color_range_end > msg.color_range_start)
|
if (should_do_colors_ && msg.color_range_end > msg.color_range_start)
|
||||||
{
|
{
|
||||||
// before color range
|
// before color range
|
||||||
print_range_(formatted, 0, msg.color_range_start);
|
print_range_(formatted, 0, msg.color_range_start);
|
||||||
|
|
||||||
// in color range
|
// in color range
|
||||||
auto orig_attribs = set_foreground_color_(colors_[msg.level]);
|
auto orig_attribs = static_cast<WORD>(set_foreground_color_(colors_[msg.level]));
|
||||||
print_range_(formatted, msg.color_range_start, msg.color_range_end);
|
print_range_(formatted, msg.color_range_start, msg.color_range_end);
|
||||||
// reset to orig colors
|
// reset to orig colors
|
||||||
::SetConsoleTextAttribute(out_handle_, orig_attribs);
|
::SetConsoleTextAttribute(static_cast<HANDLE>(out_handle_), orig_attribs);
|
||||||
print_range_(formatted, msg.color_range_end, formatted.size());
|
print_range_(formatted, msg.color_range_end, formatted.size());
|
||||||
}
|
}
|
||||||
else // print without colors if color range is invalid (or color is disabled)
|
else // print without colors if color range is invalid (or color is disabled)
|
||||||
{
|
{
|
||||||
print_range_(formatted, 0, formatted.size());
|
write_to_file_(formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,66 +101,63 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<sp
|
|||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode)
|
||||||
{
|
{
|
||||||
switch (mode)
|
std::lock_guard<mutex_t> lock(mutex_);
|
||||||
|
set_color_mode_impl(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ConsoleMutex>
|
||||||
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode_impl(color_mode mode)
|
||||||
{
|
{
|
||||||
case color_mode::always:
|
if (mode == color_mode::automatic)
|
||||||
case color_mode::automatic:
|
{
|
||||||
should_do_colors_ = true;
|
// should do colors only if out_handle_ points to actual console.
|
||||||
break;
|
DWORD console_mode;
|
||||||
case color_mode::never:
|
bool in_console = ::GetConsoleMode(static_cast<HANDLE>(out_handle_), &console_mode) != 0;
|
||||||
should_do_colors_ = false;
|
should_do_colors_ = in_console;
|
||||||
break;
|
}
|
||||||
default:
|
else
|
||||||
should_do_colors_ = true;
|
{
|
||||||
|
should_do_colors_ = mode == color_mode::always ? true : false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set foreground color and return the orig console attributes (for resetting later)
|
// set foreground color and return the orig console attributes (for resetting later)
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
WORD SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(WORD attribs)
|
std::uint16_t SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(std::uint16_t attribs)
|
||||||
{
|
{
|
||||||
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info;
|
CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info;
|
||||||
::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info);
|
if (!::GetConsoleScreenBufferInfo(static_cast<HANDLE>(out_handle_), &orig_buffer_info))
|
||||||
WORD back_color = orig_buffer_info.wAttributes;
|
{
|
||||||
// retrieve the current background color
|
// just return white if failed getting console info
|
||||||
back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY));
|
return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
||||||
// keep the background color unchanged
|
}
|
||||||
::SetConsoleTextAttribute(out_handle_, attribs | back_color);
|
|
||||||
return orig_buffer_info.wAttributes; // return orig attribs
|
// change only the foreground bits (lowest 4 bits)
|
||||||
|
auto new_attribs = static_cast<WORD>(attribs) | (orig_buffer_info.wAttributes & 0xfff0);
|
||||||
|
auto ignored = ::SetConsoleTextAttribute(static_cast<HANDLE>(out_handle_), static_cast<WORD>(new_attribs));
|
||||||
|
(void)(ignored);
|
||||||
|
return static_cast<std::uint16_t>(orig_buffer_info.wAttributes); // return orig attribs
|
||||||
}
|
}
|
||||||
|
|
||||||
// print a range of formatted message to console
|
// print a range of formatted message to console
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end)
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end)
|
||||||
|
{
|
||||||
|
if (end > start)
|
||||||
{
|
{
|
||||||
auto size = static_cast<DWORD>(end - start);
|
auto size = static_cast<DWORD>(end - start);
|
||||||
::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr);
|
auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start, size, nullptr, nullptr);
|
||||||
|
(void)(ignored);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted)
|
void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted)
|
||||||
{
|
{
|
||||||
if (out_handle_ == nullptr) // no console and no file redirect
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto size = static_cast<DWORD>(formatted.size());
|
auto size = static_cast<DWORD>(formatted.size());
|
||||||
if (size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD total_written = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
DWORD bytes_written = 0;
|
DWORD bytes_written = 0;
|
||||||
bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0;
|
auto ignored = ::WriteFile(static_cast<HANDLE>(out_handle_), formatted.data(), size, &bytes_written, nullptr);
|
||||||
if (!ok || bytes_written == 0)
|
(void)(ignored);
|
||||||
{
|
|
||||||
throw_spdlog_ex("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()));
|
|
||||||
}
|
|
||||||
total_written += bytes_written;
|
|
||||||
} while (total_written < size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wincolor_stdout_sink
|
// wincolor_stdout_sink
|
||||||
@@ -175,6 +171,5 @@ template<typename ConsoleMutex>
|
|||||||
SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode)
|
SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode)
|
||||||
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode)
|
: wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
} // namespace sinks
|
} // namespace sinks
|
||||||
} // namespace spdlog
|
} // namespace spdlog
|
||||||
|
|||||||
@@ -12,9 +12,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include <spdlog/details/windows_include.h>
|
|
||||||
#include <wincon.h>
|
|
||||||
|
|
||||||
namespace spdlog {
|
namespace spdlog {
|
||||||
namespace sinks {
|
namespace sinks {
|
||||||
@@ -26,21 +24,14 @@ template<typename ConsoleMutex>
|
|||||||
class wincolor_sink : public sink
|
class wincolor_sink : public sink
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
const WORD BOLD = FOREGROUND_INTENSITY;
|
wincolor_sink(void *out_handle, color_mode mode);
|
||||||
const WORD RED = FOREGROUND_RED;
|
|
||||||
const WORD GREEN = FOREGROUND_GREEN;
|
|
||||||
const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
||||||
const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
||||||
const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN;
|
|
||||||
|
|
||||||
wincolor_sink(HANDLE out_handle, color_mode mode);
|
|
||||||
~wincolor_sink() override;
|
~wincolor_sink() override;
|
||||||
|
|
||||||
wincolor_sink(const wincolor_sink &other) = delete;
|
wincolor_sink(const wincolor_sink &other) = delete;
|
||||||
wincolor_sink &operator=(const wincolor_sink &other) = delete;
|
wincolor_sink &operator=(const wincolor_sink &other) = delete;
|
||||||
|
|
||||||
// change the color for the given level
|
// change the color for the given level
|
||||||
void set_color(level::level_enum level, WORD 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) final override;
|
||||||
void flush() final override;
|
void flush() final override;
|
||||||
void set_pattern(const std::string &pattern) override final;
|
void set_pattern(const std::string &pattern) override final;
|
||||||
@@ -49,21 +40,22 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
using mutex_t = typename ConsoleMutex::mutex_t;
|
using mutex_t = typename ConsoleMutex::mutex_t;
|
||||||
HANDLE out_handle_;
|
void *out_handle_;
|
||||||
mutex_t &mutex_;
|
mutex_t &mutex_;
|
||||||
bool in_console_;
|
|
||||||
bool should_do_colors_;
|
bool should_do_colors_;
|
||||||
std::unique_ptr<spdlog::formatter> formatter_;
|
std::unique_ptr<spdlog::formatter> formatter_;
|
||||||
std::array<WORD, level::n_levels> colors_;
|
std::array<std::uint16_t, level::n_levels> colors_;
|
||||||
|
|
||||||
// set foreground color and return the orig console attributes (for resetting later)
|
// set foreground color and return the orig console attributes (for resetting later)
|
||||||
WORD set_foreground_color_(WORD attribs);
|
std::uint16_t set_foreground_color_(std::uint16_t attribs);
|
||||||
|
|
||||||
// print a range of formatted message to console
|
// print a range of formatted message to console
|
||||||
void print_range_(const memory_buf_t &formatted, size_t start, size_t end);
|
void print_range_(const memory_buf_t &formatted, size_t start, size_t end);
|
||||||
|
|
||||||
// in case we are redirected to file (not in console mode)
|
// in case we are redirected to file (not in console mode)
|
||||||
void write_to_file_(const memory_buf_t &formatted);
|
void write_to_file_(const memory_buf_t &formatted);
|
||||||
|
|
||||||
|
void set_color_mode_impl(color_mode mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename ConsoleMutex>
|
template<typename ConsoleMutex>
|
||||||
@@ -85,7 +77,6 @@ using wincolor_stdout_sink_st = wincolor_stdout_sink<details::console_nullmutex>
|
|||||||
|
|
||||||
using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>;
|
using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>;
|
||||||
using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>;
|
using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>;
|
||||||
|
|
||||||
} // namespace sinks
|
} // namespace sinks
|
||||||
} // namespace spdlog
|
} // namespace spdlog
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ SPDLOG_INLINE void dump_backtrace()
|
|||||||
default_logger_raw()->dump_backtrace();
|
default_logger_raw()->dump_backtrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SPDLOG_INLINE level::level_enum get_level()
|
||||||
|
{
|
||||||
|
return default_logger_raw()->level();
|
||||||
|
}
|
||||||
|
|
||||||
|
SPDLOG_INLINE bool should_log(level::level_enum log_level)
|
||||||
|
{
|
||||||
|
return default_logger_raw()->should_log(log_level);
|
||||||
|
}
|
||||||
|
|
||||||
SPDLOG_INLINE void set_level(level::level_enum log_level)
|
SPDLOG_INLINE void set_level(level::level_enum log_level)
|
||||||
{
|
{
|
||||||
details::registry::instance().set_level(log_level);
|
details::registry::instance().set_level(log_level);
|
||||||
|
|||||||
@@ -67,9 +67,15 @@ SPDLOG_API void disable_backtrace();
|
|||||||
// call dump backtrace on default logger
|
// call dump backtrace on default logger
|
||||||
SPDLOG_API void dump_backtrace();
|
SPDLOG_API void dump_backtrace();
|
||||||
|
|
||||||
|
// Get global logging level
|
||||||
|
SPDLOG_API level::level_enum get_level();
|
||||||
|
|
||||||
// Set global logging level
|
// Set 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
|
||||||
|
SPDLOG_API bool should_log(level::level_enum lvl);
|
||||||
|
|
||||||
// Set global flush level
|
// Set global flush level
|
||||||
SPDLOG_API void flush_on(level::level_enum log_level);
|
SPDLOG_API void flush_on(level::level_enum log_level);
|
||||||
|
|
||||||
@@ -105,7 +111,7 @@ SPDLOG_API void set_automatic_registration(bool automatic_registration);
|
|||||||
//
|
//
|
||||||
// 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 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.
|
||||||
@@ -122,51 +128,51 @@ SPDLOG_API spdlog::logger *default_logger_raw();
|
|||||||
SPDLOG_API void set_default_logger(std::shared_ptr<spdlog::logger> default_logger);
|
SPDLOG_API void set_default_logger(std::shared_ptr<spdlog::logger> default_logger);
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void log(source_loc source, level::level_enum lvl, const FormatString &fmt, const Args &... args)
|
inline void log(source_loc source, level::level_enum lvl, const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->log(source, lvl, fmt, args...);
|
default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void log(level::level_enum lvl, const FormatString &fmt, const Args &... args)
|
inline void log(level::level_enum lvl, const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->log(source_loc{}, lvl, fmt, args...);
|
default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void trace(const FormatString &fmt, const Args &... args)
|
inline void trace(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->trace(fmt, args...);
|
default_logger_raw()->trace(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void debug(const FormatString &fmt, const Args &... args)
|
inline void debug(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->debug(fmt, args...);
|
default_logger_raw()->debug(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void info(const FormatString &fmt, const Args &... args)
|
inline void info(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->info(fmt, args...);
|
default_logger_raw()->info(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void warn(const FormatString &fmt, const Args &... args)
|
inline void warn(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->warn(fmt, args...);
|
default_logger_raw()->warn(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void error(const FormatString &fmt, const Args &... args)
|
inline void error(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->error(fmt, args...);
|
default_logger_raw()->error(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename FormatString, typename... Args>
|
template<typename FormatString, typename... Args>
|
||||||
inline void critical(const FormatString &fmt, const Args &... args)
|
inline void critical(const FormatString &fmt, Args &&...args)
|
||||||
{
|
{
|
||||||
default_logger_raw()->critical(fmt, args...);
|
default_logger_raw()->critical(fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|||||||
61
include/spdlog/stopwatch.h
Normal file
61
include/spdlog/stopwatch.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
|
||||||
|
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <spdlog/fmt/fmt.h>
|
||||||
|
|
||||||
|
// Stopwatch support for spdlog (using std::chrono::steady_clock).
|
||||||
|
// Displays elapsed seconds since construction as double.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// spdlog::stopwatch sw;
|
||||||
|
// ...
|
||||||
|
// spdlog::debug("Elapsed: {} seconds", sw); => "Elapsed 0.005116733 seconds"
|
||||||
|
// spdlog::info("Elapsed: {:.6} seconds", sw); => "Elapsed 0.005163 seconds"
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// If other units are needed (e.g. millis instead of double), include "fmt/chrono.h" and use "duration_cast<..>(sw.elapsed())":
|
||||||
|
//
|
||||||
|
// #include <spdlog/fmt/chrono.h>
|
||||||
|
//..
|
||||||
|
// using std::chrono::duration_cast;
|
||||||
|
// using std::chrono::milliseconds;
|
||||||
|
// spdlog::info("Elapsed {}", duration_cast<milliseconds>(sw.elapsed())); => "Elapsed 5ms"
|
||||||
|
|
||||||
|
namespace spdlog {
|
||||||
|
class stopwatch
|
||||||
|
{
|
||||||
|
using clock = std::chrono::steady_clock;
|
||||||
|
std::chrono::time_point<clock> start_tp_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
stopwatch()
|
||||||
|
: start_tp_{clock::now()}
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::chrono::duration<double> elapsed() const
|
||||||
|
{
|
||||||
|
return std::chrono::duration<double>(clock::now() - start_tp_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset()
|
||||||
|
{
|
||||||
|
start_tp_ = clock ::now();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace spdlog
|
||||||
|
|
||||||
|
// Support for fmt formatting (e.g. "{:012.9}" or just "{}")
|
||||||
|
namespace fmt {
|
||||||
|
template<>
|
||||||
|
struct formatter<spdlog::stopwatch> : formatter<double>
|
||||||
|
{
|
||||||
|
template<typename FormatContext>
|
||||||
|
auto format(const spdlog::stopwatch &sw, FormatContext &ctx) -> decltype(ctx.out())
|
||||||
|
{
|
||||||
|
return formatter<double>::format(sw.elapsed().count(), ctx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace fmt
|
||||||
@@ -58,6 +58,14 @@
|
|||||||
// #define SPDLOG_EOL ";-)\n"
|
// #define SPDLOG_EOL ";-)\n"
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Uncomment to override default folder separators ("/" or "\\/" under
|
||||||
|
// Linux/Windows). Each character in the string is treated as a different
|
||||||
|
// separator.
|
||||||
|
//
|
||||||
|
// #define SPDLOG_FOLDER_SEPS "\\"
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Uncomment to use your own copy of the fmt library instead of spdlog's copy.
|
// Uncomment to use your own copy of the fmt library instead of spdlog's copy.
|
||||||
// In this case spdlog will try to include <fmt/format.h> so set your -I flag
|
// In this case spdlog will try to include <fmt/format.h> so set your -I flag
|
||||||
@@ -79,7 +87,7 @@
|
|||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Uncomment to customize level names (e.g. "MT TRACE")
|
// Uncomment to customize level names (e.g. "MY TRACE")
|
||||||
//
|
//
|
||||||
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING",
|
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING",
|
||||||
// "MY ERROR", "MY CRITICAL", "OFF" }
|
// "MY ERROR", "MY CRITICAL", "OFF" }
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define SPDLOG_VER_MAJOR 1
|
#define SPDLOG_VER_MAJOR 1
|
||||||
#define SPDLOG_VER_MINOR 7
|
#define SPDLOG_VER_MINOR 9
|
||||||
#define SPDLOG_VER_PATCH 0
|
#define SPDLOG_VER_PATCH 0
|
||||||
|
|
||||||
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH)
|
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH)
|
||||||
|
|||||||
@@ -14,4 +14,4 @@ with open(config_h, 'r') as fp:
|
|||||||
if m:
|
if m:
|
||||||
data[m.group(1)] = int(m.group(2))
|
data[m.group(1)] = int(m.group(2))
|
||||||
|
|
||||||
print('{}.{}.{}'.format(data['MAJOR'], data['MINOR'], data['PATCH']))
|
print(f"{data['MAJOR']}.{data['MINOR']}.{data['PATCH']}")
|
||||||
|
|||||||
171
src/fmt.cpp
171
src/fmt.cpp
@@ -10,12 +10,12 @@
|
|||||||
# include <spdlog/fmt/bundled/format-inl.h>
|
# include <spdlog/fmt/bundled/format-inl.h>
|
||||||
|
|
||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace internal {
|
namespace detail {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
int format_float(char *buf, std::size_t size, const char *format, int precision, T value)
|
int format_float(char *buf, std::size_t size, const char *format, int precision, T value)
|
||||||
{
|
{
|
||||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
# ifdef FMT_FUZZ
|
||||||
if (precision > 100000)
|
if (precision > 100000)
|
||||||
throw std::runtime_error("fuzz mode - avoid large allocation inside snprintf");
|
throw std::runtime_error("fuzz mode - avoid large allocation inside snprintf");
|
||||||
# endif
|
# endif
|
||||||
@@ -23,167 +23,46 @@ int format_float(char *buf, std::size_t size, const char *format, int precision,
|
|||||||
int (*snprintf_ptr)(char *, size_t, const char *, ...) = FMT_SNPRINTF;
|
int (*snprintf_ptr)(char *, size_t, const char *, ...) = FMT_SNPRINTF;
|
||||||
return precision < 0 ? snprintf_ptr(buf, size, format, value) : snprintf_ptr(buf, size, format, precision, value);
|
return precision < 0 ? snprintf_ptr(buf, size, format, value) : snprintf_ptr(buf, size, format, precision, value);
|
||||||
}
|
}
|
||||||
struct sprintf_specs
|
|
||||||
{
|
|
||||||
int precision;
|
|
||||||
char type;
|
|
||||||
bool alt : 1;
|
|
||||||
|
|
||||||
template<typename Char>
|
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x) FMT_NOEXCEPT;
|
||||||
constexpr sprintf_specs(basic_format_specs<Char> specs)
|
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x) FMT_NOEXCEPT;
|
||||||
: precision(specs.precision)
|
} // namespace detail
|
||||||
, type(specs.type)
|
|
||||||
, alt(specs.alt)
|
|
||||||
{}
|
|
||||||
|
|
||||||
constexpr bool has_precision() const
|
|
||||||
{
|
|
||||||
return precision >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is deprecated and is kept only to preserve ABI compatibility.
|
|
||||||
template<typename Double>
|
|
||||||
char *sprintf_format(Double value, internal::buffer<char> &buf, sprintf_specs specs)
|
|
||||||
{
|
|
||||||
// Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
|
|
||||||
FMT_ASSERT(buf.capacity() != 0, "empty buffer");
|
|
||||||
|
|
||||||
// Build format string.
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
max_format_size = 10
|
|
||||||
}; // longest format: %#-*.*Lg
|
|
||||||
char format[max_format_size];
|
|
||||||
char *format_ptr = format;
|
|
||||||
*format_ptr++ = '%';
|
|
||||||
if (specs.alt || !specs.type)
|
|
||||||
*format_ptr++ = '#';
|
|
||||||
if (specs.precision >= 0)
|
|
||||||
{
|
|
||||||
*format_ptr++ = '.';
|
|
||||||
*format_ptr++ = '*';
|
|
||||||
}
|
|
||||||
if (std::is_same<Double, long double>::value)
|
|
||||||
*format_ptr++ = 'L';
|
|
||||||
|
|
||||||
char type = specs.type;
|
|
||||||
|
|
||||||
if (type == '%')
|
|
||||||
type = 'f';
|
|
||||||
else if (type == 0 || type == 'n')
|
|
||||||
type = 'g';
|
|
||||||
#if FMT_MSC_VER
|
|
||||||
if (type == 'F')
|
|
||||||
{
|
|
||||||
// MSVC's printf doesn't support 'F'.
|
|
||||||
type = 'f';
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
*format_ptr++ = type;
|
|
||||||
*format_ptr = '\0';
|
|
||||||
|
|
||||||
// Format using snprintf.
|
|
||||||
char *start = nullptr;
|
|
||||||
char *decimal_point_pos = nullptr;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
std::size_t buffer_size = buf.capacity();
|
|
||||||
start = &buf[0];
|
|
||||||
int result = format_float(start, buffer_size, format, specs.precision, value);
|
|
||||||
if (result >= 0)
|
|
||||||
{
|
|
||||||
unsigned n = internal::to_unsigned(result);
|
|
||||||
if (n < buf.capacity())
|
|
||||||
{
|
|
||||||
// Find the decimal point.
|
|
||||||
auto p = buf.data(), end = p + n;
|
|
||||||
if (*p == '+' || *p == '-')
|
|
||||||
++p;
|
|
||||||
if (specs.type != 'a' && specs.type != 'A')
|
|
||||||
{
|
|
||||||
while (p < end && *p >= '0' && *p <= '9')
|
|
||||||
++p;
|
|
||||||
if (p < end && *p != 'e' && *p != 'E')
|
|
||||||
{
|
|
||||||
decimal_point_pos = p;
|
|
||||||
if (!specs.type)
|
|
||||||
{
|
|
||||||
// Keep only one trailing zero after the decimal point.
|
|
||||||
++p;
|
|
||||||
if (*p == '0')
|
|
||||||
++p;
|
|
||||||
while (p != end && *p >= '1' && *p <= '9')
|
|
||||||
++p;
|
|
||||||
char *where = p;
|
|
||||||
while (p != end && *p == '0')
|
|
||||||
++p;
|
|
||||||
if (p == end || *p < '0' || *p > '9')
|
|
||||||
{
|
|
||||||
if (p != end)
|
|
||||||
std::memmove(where, p, to_unsigned(end - p));
|
|
||||||
n -= static_cast<unsigned>(p - where);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.resize(n);
|
|
||||||
break; // The buffer is large enough - continue with formatting.
|
|
||||||
}
|
|
||||||
buf.reserve(n + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If result is negative we ask to increase the capacity by at least 1,
|
|
||||||
// but as std::vector, the buffer grows exponentially.
|
|
||||||
buf.reserve(buf.capacity() + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return decimal_point_pos;
|
|
||||||
}
|
|
||||||
} // namespace internal
|
|
||||||
|
|
||||||
template FMT_API char *internal::sprintf_format(double, internal::buffer<char> &, sprintf_specs);
|
|
||||||
template FMT_API char *internal::sprintf_format(long double, internal::buffer<char> &, sprintf_specs);
|
|
||||||
|
|
||||||
template struct FMT_INSTANTIATION_DEF_API internal::basic_data<void>;
|
|
||||||
|
|
||||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||||
int (*instantiate_format_float)(double, int, internal::float_specs, internal::buffer<char> &) = internal::format_float;
|
int (*instantiate_format_float)(double, int, detail::float_specs, detail::buffer<char> &) = detail::format_float;
|
||||||
|
|
||||||
# ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
# ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||||
template FMT_API internal::locale_ref::locale_ref(const std::locale &loc);
|
template FMT_API detail::locale_ref::locale_ref(const std::locale &loc);
|
||||||
template FMT_API std::locale internal::locale_ref::get<std::locale>() const;
|
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||||
# endif
|
# endif
|
||||||
|
|
||||||
// Explicit instantiations for char.
|
// Explicit instantiations for char.
|
||||||
|
|
||||||
template FMT_API std::string internal::grouping_impl<char>(locale_ref);
|
template FMT_API auto detail::thousands_sep_impl(locale_ref) -> thousands_sep_result<char>;
|
||||||
template FMT_API char internal::thousands_sep_impl(locale_ref);
|
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||||
template FMT_API char internal::decimal_point_impl(locale_ref);
|
|
||||||
|
|
||||||
template FMT_API void internal::buffer<char>::append(const char *, const char *);
|
template FMT_API void detail::buffer<char>::append(const char *, const char *);
|
||||||
|
|
||||||
template FMT_API void internal::arg_map<format_context>::init(const basic_format_args<format_context> &args);
|
// DEPRECATED!
|
||||||
|
// There is no correspondent extern template in format.h because of
|
||||||
|
// incompatibility between clang and gcc (#2377).
|
||||||
|
template FMT_API void detail::vformat_to(
|
||||||
|
detail::buffer<char> &, string_view, basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||||
|
|
||||||
template FMT_API std::string internal::vformat<char>(string_view, basic_format_args<format_context>);
|
template FMT_API int detail::snprintf_float(double, int, detail::float_specs, detail::buffer<char> &);
|
||||||
|
template FMT_API int detail::snprintf_float(long double, int, detail::float_specs, detail::buffer<char> &);
|
||||||
template FMT_API format_context::iterator internal::vformat_to(internal::buffer<char> &, string_view, basic_format_args<format_context>);
|
template FMT_API int detail::format_float(double, int, detail::float_specs, detail::buffer<char> &);
|
||||||
|
template FMT_API int detail::format_float(long double, int, detail::float_specs, detail::buffer<char> &);
|
||||||
template FMT_API int internal::snprintf_float(double, int, internal::float_specs, internal::buffer<char> &);
|
|
||||||
template FMT_API int internal::snprintf_float(long double, int, internal::float_specs, internal::buffer<char> &);
|
|
||||||
template FMT_API int internal::format_float(double, int, internal::float_specs, internal::buffer<char> &);
|
|
||||||
template FMT_API int internal::format_float(long double, int, internal::float_specs, internal::buffer<char> &);
|
|
||||||
|
|
||||||
// Explicit instantiations for wchar_t.
|
// Explicit instantiations for wchar_t.
|
||||||
|
|
||||||
template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
|
template FMT_API auto detail::thousands_sep_impl(locale_ref) -> thousands_sep_result<wchar_t>;
|
||||||
template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
|
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||||
template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
|
|
||||||
|
|
||||||
template FMT_API void internal::buffer<wchar_t>::append(const wchar_t *, const wchar_t *);
|
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t *, const wchar_t *);
|
||||||
|
|
||||||
|
template struct detail::basic_data<void>;
|
||||||
|
|
||||||
template FMT_API std::wstring internal::vformat<wchar_t>(wstring_view, basic_format_args<wformat_context>);
|
|
||||||
FMT_END_NAMESPACE
|
FMT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // !SPDLOG_FMT_EXTERNAL
|
#endif // !SPDLOG_FMT_EXTERNAL
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
project(spdlog_utests CXX)
|
project(spdlog_utests CXX)
|
||||||
|
|
||||||
if(NOT TARGET spdlog)
|
if(NOT TARGET spdlog)
|
||||||
@@ -33,7 +32,8 @@ set(SPDLOG_UTESTS_SOURCES
|
|||||||
test_backtrace.cpp
|
test_backtrace.cpp
|
||||||
test_create_dir.cpp
|
test_create_dir.cpp
|
||||||
test_cfg.cpp
|
test_cfg.cpp
|
||||||
test_time_point.cpp)
|
test_time_point.cpp
|
||||||
|
test_stopwatch.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)
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "spdlog/sinks/basic_file_sink.h"
|
#include "spdlog/sinks/basic_file_sink.h"
|
||||||
#include "test_sink.h"
|
#include "test_sink.h"
|
||||||
|
|
||||||
|
#define TEST_FILENAME "test_logs/async_test.log"
|
||||||
|
|
||||||
TEST_CASE("basic async test ", "[async]")
|
TEST_CASE("basic async test ", "[async]")
|
||||||
{
|
{
|
||||||
auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>();
|
auto test_sink = std::make_shared<spdlog::sinks::test_sink_mt>();
|
||||||
@@ -87,7 +89,7 @@ TEST_CASE("async periodic flush", "[async]")
|
|||||||
auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]);
|
auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]);
|
||||||
|
|
||||||
spdlog::flush_every(std::chrono::seconds(1));
|
spdlog::flush_every(std::chrono::seconds(1));
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1100));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1700));
|
||||||
REQUIRE(test_sink->flush_counter() == 1);
|
REQUIRE(test_sink->flush_counter() == 1);
|
||||||
spdlog::flush_every(std::chrono::seconds(0));
|
spdlog::flush_every(std::chrono::seconds(0));
|
||||||
spdlog::drop_all();
|
spdlog::drop_all();
|
||||||
@@ -149,7 +151,7 @@ TEST_CASE("to_file", "[async]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
size_t messages = 1024;
|
size_t messages = 1024;
|
||||||
size_t tp_threads = 1;
|
size_t tp_threads = 1;
|
||||||
std::string filename = "test_logs/async_test.log";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
{
|
{
|
||||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
|
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
|
||||||
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
|
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
|
||||||
@@ -161,8 +163,8 @@ TEST_CASE("to_file", "[async]")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require_message_count(filename, messages);
|
require_message_count(TEST_FILENAME, messages);
|
||||||
auto contents = file_contents(filename);
|
auto contents = file_contents(TEST_FILENAME);
|
||||||
using spdlog::details::os::default_eol;
|
using spdlog::details::os::default_eol;
|
||||||
REQUIRE(ends_with(contents, fmt::format("Hello message #1023{}", default_eol)));
|
REQUIRE(ends_with(contents, fmt::format("Hello message #1023{}", default_eol)));
|
||||||
}
|
}
|
||||||
@@ -172,7 +174,7 @@ TEST_CASE("to_file multi-workers", "[async]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
size_t messages = 1024 * 10;
|
size_t messages = 1024 * 10;
|
||||||
size_t tp_threads = 10;
|
size_t tp_threads = 10;
|
||||||
std::string filename = "test_logs/async_test.log";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
{
|
{
|
||||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
|
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filename, true);
|
||||||
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
|
auto tp = std::make_shared<spdlog::details::thread_pool>(messages, tp_threads);
|
||||||
@@ -184,5 +186,5 @@ TEST_CASE("to_file multi-workers", "[async]")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require_message_count(filename, messages);
|
require_message_count(TEST_FILENAME, messages);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ TEST_CASE("bactrace-async", "[bactrace]")
|
|||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
logger->debug("debug message {}", i);
|
logger->debug("debug message {}", i);
|
||||||
|
|
||||||
sleep_for_millis(10);
|
sleep_for_millis(100);
|
||||||
REQUIRE(test_sink->lines().size() == 1);
|
REQUIRE(test_sink->lines().size() == 1);
|
||||||
REQUIRE(test_sink->lines()[0] == "info message");
|
REQUIRE(test_sink->lines()[0] == "info message");
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
#include "test_sink.h"
|
#include "test_sink.h"
|
||||||
|
|
||||||
@@ -41,21 +42,23 @@ TEST_CASE("argv2", "[cfg]")
|
|||||||
auto l1 = spdlog::create<test_sink_st>("l1");
|
auto l1 = spdlog::create<test_sink_st>("l1");
|
||||||
REQUIRE(l1->level() == spdlog::level::warn);
|
REQUIRE(l1->level() == spdlog::level::warn);
|
||||||
REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace);
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace);
|
||||||
spdlog::set_level(spdlog::level::info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("argv3", "[cfg]")
|
TEST_CASE("argv3", "[cfg]")
|
||||||
{
|
{
|
||||||
|
spdlog::set_level(spdlog::level::trace);
|
||||||
|
|
||||||
spdlog::drop("l1");
|
spdlog::drop("l1");
|
||||||
const char *argv[] = {"ignore", "SPDLOG_LEVEL="};
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk_name=warn"};
|
||||||
load_argv_levels(2, argv);
|
load_argv_levels(2, argv);
|
||||||
auto l1 = spdlog::create<test_sink_st>("l1");
|
auto l1 = spdlog::create<test_sink_st>("l1");
|
||||||
REQUIRE(l1->level() == spdlog::level::info);
|
REQUIRE(l1->level() == spdlog::level::trace);
|
||||||
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("argv4", "[cfg]")
|
TEST_CASE("argv4", "[cfg]")
|
||||||
{
|
{
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
spdlog::drop("l1");
|
spdlog::drop("l1");
|
||||||
const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk"};
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk"};
|
||||||
load_argv_levels(2, argv);
|
load_argv_levels(2, argv);
|
||||||
@@ -65,6 +68,7 @@ TEST_CASE("argv4", "[cfg]")
|
|||||||
|
|
||||||
TEST_CASE("argv5", "[cfg]")
|
TEST_CASE("argv5", "[cfg]")
|
||||||
{
|
{
|
||||||
|
spdlog::set_level(spdlog::level::info);
|
||||||
spdlog::drop("l1");
|
spdlog::drop("l1");
|
||||||
const char *argv[] = {"ignore", "ignore", "SPDLOG_LEVEL=l1=warn,trace"};
|
const char *argv[] = {"ignore", "ignore", "SPDLOG_LEVEL=l1=warn,trace"};
|
||||||
load_argv_levels(3, argv);
|
load_argv_levels(3, argv);
|
||||||
@@ -91,3 +95,89 @@ TEST_CASE("argv7", "[cfg]")
|
|||||||
REQUIRE(spdlog::default_logger()->level() == spdlog::level::err);
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::err);
|
||||||
spdlog::set_level(spdlog::level::info);
|
spdlog::set_level(spdlog::level::info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("level-not-set-test1", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
const char *argv[] = {"ignore", ""};
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
|
||||||
|
l1->set_level(spdlog::level::trace);
|
||||||
|
REQUIRE(l1->level() == spdlog::level::trace);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("level-not-set-test2", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
spdlog::drop("l2");
|
||||||
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"};
|
||||||
|
|
||||||
|
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
|
||||||
|
l1->set_level(spdlog::level::warn);
|
||||||
|
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
|
||||||
|
l2->set_level(spdlog::level::warn);
|
||||||
|
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
|
||||||
|
REQUIRE(l1->level() == spdlog::level::trace);
|
||||||
|
REQUIRE(l2->level() == spdlog::level::warn);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("level-not-set-test3", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
spdlog::drop("l2");
|
||||||
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace"};
|
||||||
|
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
|
||||||
|
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
|
||||||
|
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
|
||||||
|
|
||||||
|
REQUIRE(l1->level() == spdlog::level::trace);
|
||||||
|
REQUIRE(l2->level() == spdlog::level::info);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("level-not-set-test4", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
spdlog::drop("l2");
|
||||||
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=trace,warn"};
|
||||||
|
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
|
||||||
|
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
|
||||||
|
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
|
||||||
|
|
||||||
|
REQUIRE(l1->level() == spdlog::level::trace);
|
||||||
|
REQUIRE(l2->level() == spdlog::level::warn);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("level-not-set-test5", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
spdlog::drop("l2");
|
||||||
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=junk,warn"};
|
||||||
|
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
|
||||||
|
auto l1 = spdlog::create<spdlog::sinks::test_sink_st>("l1");
|
||||||
|
auto l2 = spdlog::create<spdlog::sinks::test_sink_st>("l2");
|
||||||
|
|
||||||
|
REQUIRE(l1->level() == spdlog::level::warn);
|
||||||
|
REQUIRE(l2->level() == spdlog::level::warn);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::warn);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("restore-to-default", "[cfg]")
|
||||||
|
{
|
||||||
|
spdlog::drop("l1");
|
||||||
|
spdlog::drop("l2");
|
||||||
|
const char *argv[] = {"ignore", "SPDLOG_LEVEL=info"};
|
||||||
|
load_argv_levels(2, argv);
|
||||||
|
REQUIRE(spdlog::default_logger()->level() == spdlog::level::info);
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
using spdlog::details::os::create_dir;
|
using spdlog::details::os::create_dir;
|
||||||
using spdlog::details::os::path_exists;
|
using spdlog::details::os::path_exists;
|
||||||
|
|
||||||
bool try_create_dir(const char *path, const char *normalized_path)
|
bool try_create_dir(const spdlog::filename_t &path, const spdlog::filename_t &normalized_path)
|
||||||
{
|
{
|
||||||
auto rv = create_dir(path);
|
auto rv = create_dir(path);
|
||||||
REQUIRE(rv == true);
|
REQUIRE(rv == true);
|
||||||
@@ -17,24 +17,25 @@ TEST_CASE("create_dir", "[create_dir]")
|
|||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
|
|
||||||
REQUIRE(try_create_dir("test_logs/dir1/dir1", "test_logs/dir1/dir1"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), SPDLOG_FILENAME_T("test_logs/dir1/dir1")));
|
||||||
REQUIRE(try_create_dir("test_logs/dir1/dir1", "test_logs/dir1/dir1")); // test existing
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); // test existing
|
||||||
REQUIRE(try_create_dir("test_logs/dir1///dir2//", "test_logs/dir1/dir2"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1///dir2//"), SPDLOG_FILENAME_T("test_logs/dir1/dir2")));
|
||||||
REQUIRE(try_create_dir("./test_logs/dir1/dir3", "test_logs/dir1/dir3"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("./test_logs/dir1/dir3"), SPDLOG_FILENAME_T("test_logs/dir1/dir3")));
|
||||||
REQUIRE(try_create_dir("test_logs/../test_logs/dir1/dir4", "test_logs/dir1/dir4"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/../test_logs/dir1/dir4"), SPDLOG_FILENAME_T("test_logs/dir1/dir4")));
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
// test backslash folder separator
|
// test backslash folder separator
|
||||||
REQUIRE(try_create_dir("test_logs\\dir1\\dir222", "test_logs\\dir1\\dir222"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir222")));
|
||||||
REQUIRE(try_create_dir("test_logs\\dir1\\dir223\\", "test_logs\\dir1\\dir223\\"));
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\")));
|
||||||
REQUIRE(try_create_dir(".\\test_logs\\dir1\\dir2\\dir99\\..\\dir23", "test_logs\\dir1\\dir2\\dir23"));
|
REQUIRE(
|
||||||
REQUIRE(try_create_dir("test_logs\\..\\test_logs\\dir1\\dir5", "test_logs\\dir1\\dir5"));
|
try_create_dir(SPDLOG_FILENAME_T(".\\test_logs\\dir1\\dir2\\dir99\\..\\dir23"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir2\\dir23")));
|
||||||
|
REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\..\\test_logs\\dir1\\dir5"), SPDLOG_FILENAME_T("test_logs\\dir1\\dir5")));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("create_invalid_dir", "[create_dir]")
|
TEST_CASE("create_invalid_dir", "[create_dir]")
|
||||||
{
|
{
|
||||||
REQUIRE(create_dir("") == false);
|
REQUIRE(create_dir(SPDLOG_FILENAME_T("")) == false);
|
||||||
REQUIRE(create_dir(spdlog::filename_t{}) == false);
|
REQUIRE(create_dir(spdlog::filename_t{}) == false);
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
REQUIRE(create_dir("/proc/spdlog-utest") == false);
|
REQUIRE(create_dir("/proc/spdlog-utest") == false);
|
||||||
@@ -44,37 +45,31 @@ TEST_CASE("create_invalid_dir", "[create_dir]")
|
|||||||
TEST_CASE("dir_name", "[create_dir]")
|
TEST_CASE("dir_name", "[create_dir]")
|
||||||
{
|
{
|
||||||
using spdlog::details::os::dir_name;
|
using spdlog::details::os::dir_name;
|
||||||
REQUIRE(dir_name("").empty());
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("")).empty());
|
||||||
REQUIRE(dir_name("dir").empty());
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir")).empty());
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
REQUIRE(dir_name(R"(dir\)") == "dir");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\)")) == SPDLOG_FILENAME_T("dir"));
|
||||||
REQUIRE(dir_name(R"(dir\\\)") == R"(dir\\)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\\\)")) == SPDLOG_FILENAME_T(R"(dir\\)"));
|
||||||
REQUIRE(dir_name(R"(dir\file)") == "dir");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file)")) == SPDLOG_FILENAME_T("dir"));
|
||||||
REQUIRE(dir_name(R"(dir/file)") == "dir");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir"));
|
||||||
REQUIRE(dir_name(R"(dir\file.txt)") == "dir");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt)")) == SPDLOG_FILENAME_T("dir"));
|
||||||
REQUIRE(dir_name(R"(dir/file)") == "dir");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir"));
|
||||||
REQUIRE(dir_name(R"(dir\file.txt\)") == R"(dir\file.txt)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt\)")) == SPDLOG_FILENAME_T(R"(dir\file.txt)"));
|
||||||
REQUIRE(dir_name(R"(dir/file.txt/)") == R"(dir\file.txt)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\dir)"));
|
||||||
REQUIRE(dir_name(R"(\dir\file.txt)") == R"(\dir)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\\dir)"));
|
||||||
REQUIRE(dir_name(R"(/dir/file.txt)") == R"(\dir)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(..\file.txt)")) == SPDLOG_FILENAME_T(".."));
|
||||||
REQUIRE(dir_name(R"(\\dir\file.txt)") == R"(\\dir)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(.\file.txt)")) == SPDLOG_FILENAME_T("."));
|
||||||
REQUIRE(dir_name(R"(//dir/file.txt)") == R"(\\dir)");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c:\\a\b\c\d\file.txt)")) == SPDLOG_FILENAME_T(R"(c:\\a\b\c\d)"));
|
||||||
REQUIRE(dir_name(R"(..\file.txt)") == "..");
|
REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(c://a/b/c/d/file.txt)")) == SPDLOG_FILENAME_T(R"(c://a/b/c/d)"));
|
||||||
REQUIRE(dir_name(R"(../file.txt)") == "..");
|
|
||||||
REQUIRE(dir_name(R"(.\file.txt)") == ".");
|
|
||||||
REQUIRE(dir_name(R"(./file.txt)") == ".");
|
|
||||||
REQUIRE(dir_name(R"(c:\\a\b\c\d\file.txt)") == R"(c:\\a\b\c\d)");
|
|
||||||
REQUIRE(dir_name(R"(c://a/b/c/d/file.txt)") == R"(c:\\a\b\c\d)");
|
|
||||||
#else
|
|
||||||
REQUIRE(dir_name("dir/") == "dir");
|
|
||||||
REQUIRE(dir_name("dir///") == "dir//");
|
|
||||||
REQUIRE(dir_name("dir/file") == "dir");
|
|
||||||
REQUIRE(dir_name("dir/file.txt") == "dir");
|
|
||||||
REQUIRE(dir_name("dir/file.txt/") == "dir/file.txt");
|
|
||||||
REQUIRE(dir_name("/dir/file.txt") == "/dir");
|
|
||||||
REQUIRE(dir_name("//dir/file.txt") == "//dir");
|
|
||||||
REQUIRE(dir_name("../file.txt") == "..");
|
|
||||||
REQUIRE(dir_name("./file.txt") == ".");
|
|
||||||
#endif
|
#endif
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/")) == SPDLOG_FILENAME_T("dir"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir///")) == SPDLOG_FILENAME_T("dir//"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file")) == SPDLOG_FILENAME_T("dir"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt")) == SPDLOG_FILENAME_T("dir"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("dir/file.txt/")) == SPDLOG_FILENAME_T("dir/file.txt"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("/dir/file.txt")) == SPDLOG_FILENAME_T("/dir"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("//dir/file.txt")) == SPDLOG_FILENAME_T("//dir"));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("../file.txt")) == SPDLOG_FILENAME_T(".."));
|
||||||
|
REQUIRE(dir_name(SPDLOG_FILENAME_T("./file.txt")) == SPDLOG_FILENAME_T("."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
|
using filename_memory_buf_t = fmt::basic_memory_buffer<spdlog::filename_t::value_type, 250>;
|
||||||
|
|
||||||
TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
|
TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
|
||||||
{
|
{
|
||||||
using sink_type = spdlog::sinks::daily_file_sink<std::mutex, spdlog::sinks::daily_filename_calculator>;
|
using sink_type = spdlog::sinks::daily_file_sink<std::mutex, spdlog::sinks::daily_filename_calculator>;
|
||||||
@@ -10,10 +12,11 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
|
|
||||||
// calculate filename (time based)
|
// calculate filename (time based)
|
||||||
std::string basename = "test_logs/daily_dateonly";
|
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
|
||||||
std::tm tm = spdlog::details::os::localtime();
|
std::tm tm = spdlog::details::os::localtime();
|
||||||
spdlog::memory_buf_t w;
|
filename_memory_buf_t w;
|
||||||
fmt::format_to(w, "{}_{:04d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
fmt::format_to(
|
||||||
|
std::back_inserter(w), SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||||
|
|
||||||
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
|
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
|
||||||
for (int i = 0; i < 10; ++i)
|
for (int i = 0; i < 10; ++i)
|
||||||
@@ -23,7 +26,13 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
|
|||||||
}
|
}
|
||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
|
#ifdef SPDLOG_WCHAR_FILENAMES
|
||||||
|
spdlog::memory_buf_t buf;
|
||||||
|
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
|
||||||
|
auto filename = fmt::to_string(buf);
|
||||||
|
#else
|
||||||
auto filename = fmt::to_string(w);
|
auto filename = fmt::to_string(w);
|
||||||
|
#endif
|
||||||
require_message_count(filename, 10);
|
require_message_count(filename, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +40,9 @@ 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)
|
||||||
{
|
{
|
||||||
spdlog::memory_buf_t w;
|
filename_memory_buf_t w;
|
||||||
fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday);
|
fmt::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
|
||||||
|
now_tm.tm_mday);
|
||||||
return fmt::to_string(w);
|
return fmt::to_string(w);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -44,10 +54,11 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
|
|
||||||
// calculate filename (time based)
|
// calculate filename (time based)
|
||||||
std::string basename = "test_logs/daily_dateonly";
|
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
|
||||||
std::tm tm = spdlog::details::os::localtime();
|
std::tm tm = spdlog::details::os::localtime();
|
||||||
spdlog::memory_buf_t w;
|
filename_memory_buf_t w;
|
||||||
fmt::format_to(w, "{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
fmt::format_to(
|
||||||
|
std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||||
|
|
||||||
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
|
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
|
||||||
for (int i = 0; i < 10; ++i)
|
for (int i = 0; i < 10; ++i)
|
||||||
@@ -57,7 +68,13 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
|
|||||||
|
|
||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
|
#ifdef SPDLOG_WCHAR_FILENAMES
|
||||||
|
spdlog::memory_buf_t buf;
|
||||||
|
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
|
||||||
|
auto filename = fmt::to_string(buf);
|
||||||
|
#else
|
||||||
auto filename = fmt::to_string(w);
|
auto filename = fmt::to_string(w);
|
||||||
|
#endif
|
||||||
require_message_count(filename, 10);
|
require_message_count(filename, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,20 +84,20 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
|
|||||||
|
|
||||||
TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]")
|
TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]")
|
||||||
{
|
{
|
||||||
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 3);
|
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 3);
|
||||||
REQUIRE(filename == "rotated.3.txt");
|
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3.txt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]")
|
TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]")
|
||||||
{
|
{
|
||||||
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated", 3);
|
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated"), 3);
|
||||||
REQUIRE(filename == "rotated.3");
|
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]")
|
TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]")
|
||||||
{
|
{
|
||||||
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 0);
|
auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 0);
|
||||||
REQUIRE(filename == "rotated.txt");
|
REQUIRE(filename == SPDLOG_FILENAME_T("rotated.txt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// regex supported only from gcc 4.9 and above
|
// regex supported only from gcc 4.9 and above
|
||||||
@@ -91,10 +108,12 @@ TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]")
|
|||||||
TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]]")
|
TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]]")
|
||||||
{
|
{
|
||||||
// daily_YYYY-MM-DD_hh-mm.txt
|
// daily_YYYY-MM-DD_hh-mm.txt
|
||||||
auto filename = spdlog::sinks::daily_filename_calculator::calc_filename("daily.txt", spdlog::details::os::localtime());
|
auto filename =
|
||||||
|
spdlog::sinks::daily_filename_calculator::calc_filename(SPDLOG_FILENAME_T("daily.txt"), spdlog::details::os::localtime());
|
||||||
// date regex based on https://www.regular-expressions.info/dates.html
|
// date regex based on https://www.regular-expressions.info/dates.html
|
||||||
std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)");
|
std::basic_regex<spdlog::filename_t::value_type> re(
|
||||||
std::smatch match;
|
SPDLOG_FILENAME_T(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)"));
|
||||||
|
std::match_results<spdlog::filename_t::const_iterator> match;
|
||||||
REQUIRE(std::regex_match(filename, match, re));
|
REQUIRE(std::regex_match(filename, match, re));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -116,7 +135,7 @@ static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_
|
|||||||
|
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
|
|
||||||
std::string basename = "test_logs/daily_rotate.txt";
|
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_rotate.txt");
|
||||||
daily_file_sink_st sink{basename, 2, 30, true, max_days};
|
daily_file_sink_st sink{basename, 2, 30, true, max_days};
|
||||||
|
|
||||||
// simulate messages with 24 intervals
|
// simulate messages with 24 intervals
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ TEST_CASE("dup_filter_test5", "[dup_filter_sink]")
|
|||||||
|
|
||||||
dup_filter_sink_mt dup_sink{std::chrono::seconds{5}};
|
dup_filter_sink_mt dup_sink{std::chrono::seconds{5}};
|
||||||
auto test_sink = std::make_shared<test_sink_mt>();
|
auto test_sink = std::make_shared<test_sink_mt>();
|
||||||
|
test_sink->set_pattern("%v");
|
||||||
dup_sink.add_sink(test_sink);
|
dup_sink.add_sink(test_sink);
|
||||||
|
|
||||||
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"});
|
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"});
|
||||||
@@ -85,4 +86,5 @@ TEST_CASE("dup_filter_test5", "[dup_filter_sink]")
|
|||||||
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"});
|
dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"});
|
||||||
|
|
||||||
REQUIRE(test_sink->msg_counter() == 3); // skip 2 messages but log the "skipped.." message before message2
|
REQUIRE(test_sink->msg_counter() == 3); // skip 2 messages but log the "skipped.." message before message2
|
||||||
|
REQUIRE(test_sink->lines()[1] == "Skipped 2 duplicate messages..");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#define SIMPLE_LOG "test_logs/simple_log.txt"
|
||||||
|
#define SIMPLE_ASYNC_LOG "test_logs/simple_async_log.txt"
|
||||||
|
|
||||||
class failing_sink : public spdlog::sinks::base_sink<std::mutex>
|
class failing_sink : public spdlog::sinks::base_sink<std::mutex>
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
@@ -22,7 +25,7 @@ protected:
|
|||||||
TEST_CASE("default_error_handler", "[errors]]")
|
TEST_CASE("default_error_handler", "[errors]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string filename = "test_logs/simple_log.txt";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
|
||||||
|
|
||||||
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true);
|
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true);
|
||||||
logger->set_pattern("%v");
|
logger->set_pattern("%v");
|
||||||
@@ -31,8 +34,8 @@ TEST_CASE("default_error_handler", "[errors]]")
|
|||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
using spdlog::details::os::default_eol;
|
using spdlog::details::os::default_eol;
|
||||||
REQUIRE(file_contents(filename) == fmt::format("Test message 2{}", default_eol));
|
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 2{}", default_eol));
|
||||||
REQUIRE(count_lines(filename) == 1);
|
REQUIRE(count_lines(SIMPLE_LOG) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct custom_ex
|
struct custom_ex
|
||||||
@@ -40,7 +43,7 @@ struct custom_ex
|
|||||||
TEST_CASE("custom_error_handler", "[errors]]")
|
TEST_CASE("custom_error_handler", "[errors]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string filename = "test_logs/simple_log.txt";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
|
||||||
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
|
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
|
||||||
logger->flush_on(spdlog::level::info);
|
logger->flush_on(spdlog::level::info);
|
||||||
logger->set_error_handler([=](const std::string &) { throw custom_ex(); });
|
logger->set_error_handler([=](const std::string &) { throw custom_ex(); });
|
||||||
@@ -48,7 +51,7 @@ TEST_CASE("custom_error_handler", "[errors]]")
|
|||||||
|
|
||||||
REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
|
REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
|
||||||
logger->info("Good message #2");
|
logger->info("Good message #2");
|
||||||
require_message_count(filename, 2);
|
require_message_count(SIMPLE_LOG, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("default_error_handler2", "[errors]]")
|
TEST_CASE("default_error_handler2", "[errors]]")
|
||||||
@@ -72,7 +75,7 @@ TEST_CASE("async_error_handler", "[errors]]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string err_msg("log failed with some msg");
|
std::string err_msg("log failed with some msg");
|
||||||
|
|
||||||
std::string filename = "test_logs/simple_async_log.txt";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_ASYNC_LOG);
|
||||||
{
|
{
|
||||||
spdlog::init_thread_pool(128, 1);
|
spdlog::init_thread_pool(128, 1);
|
||||||
auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
|
auto logger = spdlog::create_async<spdlog::sinks::basic_file_sink_mt>("logger", filename, true);
|
||||||
@@ -90,7 +93,7 @@ TEST_CASE("async_error_handler", "[errors]]")
|
|||||||
spdlog::drop("logger"); // force logger to drain the queue and shutdown
|
spdlog::drop("logger"); // force logger to drain the queue and shutdown
|
||||||
}
|
}
|
||||||
spdlog::init_thread_pool(128, 1);
|
spdlog::init_thread_pool(128, 1);
|
||||||
require_message_count(filename, 2);
|
require_message_count(SIMPLE_ASYNC_LOG, 2);
|
||||||
REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg);
|
REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +103,7 @@ TEST_CASE("async_error_handler2", "[errors]]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string err_msg("This is async handler error message");
|
std::string err_msg("This is async handler error message");
|
||||||
{
|
{
|
||||||
spdlog::details::os::create_dir("test_logs");
|
spdlog::details::os::create_dir(SPDLOG_FILENAME_T("test_logs"));
|
||||||
spdlog::init_thread_pool(128, 1);
|
spdlog::init_thread_pool(128, 1);
|
||||||
auto logger = spdlog::create_async<failing_sink>("failed_logger");
|
auto logger = spdlog::create_async<failing_sink>("failed_logger");
|
||||||
logger->set_error_handler([=](const std::string &) {
|
logger->set_error_handler([=](const std::string &) {
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ static void test_single_print(std::function<void(std::string const &)> do_log, s
|
|||||||
REQUIRE(CloseEventLog(handle_));
|
REQUIRE(CloseEventLog(handle_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} event_log{::OpenEventLog(nullptr, TEST_SOURCE)};
|
} event_log{::OpenEventLogA(nullptr, TEST_SOURCE)};
|
||||||
|
|
||||||
REQUIRE(event_log.handle_);
|
REQUIRE(event_log.handle_);
|
||||||
|
|
||||||
DWORD read_bytes{}, size_needed{};
|
DWORD read_bytes{}, size_needed{};
|
||||||
auto ok =
|
auto ok = ::ReadEventLogA(
|
||||||
::ReadEventLog(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, &read_bytes, 0, &read_bytes, &size_needed);
|
event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, &read_bytes, 0, &read_bytes, &size_needed);
|
||||||
REQUIRE(!ok);
|
REQUIRE(!ok);
|
||||||
REQUIRE(::GetLastError() == ERROR_INSUFFICIENT_BUFFER);
|
REQUIRE(::GetLastError() == ERROR_INSUFFICIENT_BUFFER);
|
||||||
|
|
||||||
std::vector<char> record_buffer(size_needed);
|
std::vector<char> record_buffer(size_needed);
|
||||||
PEVENTLOGRECORD record = (PEVENTLOGRECORD)record_buffer.data();
|
PEVENTLOGRECORD record = (PEVENTLOGRECORD)record_buffer.data();
|
||||||
|
|
||||||
ok = ::ReadEventLog(
|
ok = ::ReadEventLogA(
|
||||||
event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, record, size_needed, &read_bytes, &size_needed);
|
event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, record, size_needed, &read_bytes, &size_needed);
|
||||||
REQUIRE(ok);
|
REQUIRE(ok);
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
*/
|
*/
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
|
#define TEST_FILENAME "test_logs/file_helper_test.txt"
|
||||||
|
|
||||||
using spdlog::details::file_helper;
|
using spdlog::details::file_helper;
|
||||||
|
|
||||||
static void write_with_helper(file_helper &helper, size_t howmany)
|
static void write_with_helper(file_helper &helper, size_t howmany)
|
||||||
{
|
{
|
||||||
spdlog::memory_buf_t formatted;
|
spdlog::memory_buf_t formatted;
|
||||||
fmt::format_to(formatted, "{}", std::string(howmany, '1'));
|
fmt::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1'));
|
||||||
helper.write(formatted);
|
helper.write(formatted);
|
||||||
helper.flush();
|
helper.flush();
|
||||||
}
|
}
|
||||||
@@ -18,7 +20,7 @@ TEST_CASE("file_helper_filename", "[file_helper::filename()]]")
|
|||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
|
|
||||||
file_helper helper;
|
file_helper helper;
|
||||||
std::string target_filename = "test_logs/file_helper_test.txt";
|
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
helper.open(target_filename);
|
helper.open(target_filename);
|
||||||
REQUIRE(helper.filename() == target_filename);
|
REQUIRE(helper.filename() == target_filename);
|
||||||
}
|
}
|
||||||
@@ -26,7 +28,7 @@ TEST_CASE("file_helper_filename", "[file_helper::filename()]]")
|
|||||||
TEST_CASE("file_helper_size", "[file_helper::size()]]")
|
TEST_CASE("file_helper_size", "[file_helper::size()]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string target_filename = "test_logs/file_helper_test.txt";
|
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
size_t expected_size = 123;
|
size_t expected_size = 123;
|
||||||
{
|
{
|
||||||
file_helper helper;
|
file_helper helper;
|
||||||
@@ -34,13 +36,13 @@ TEST_CASE("file_helper_size", "[file_helper::size()]]")
|
|||||||
write_with_helper(helper, expected_size);
|
write_with_helper(helper, expected_size);
|
||||||
REQUIRE(static_cast<size_t>(helper.size()) == expected_size);
|
REQUIRE(static_cast<size_t>(helper.size()) == expected_size);
|
||||||
}
|
}
|
||||||
REQUIRE(get_filesize(target_filename) == expected_size);
|
REQUIRE(get_filesize(TEST_FILENAME) == expected_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]")
|
TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string target_filename = "test_logs/file_helper_test.txt";
|
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
file_helper helper;
|
file_helper helper;
|
||||||
helper.open(target_filename);
|
helper.open(target_filename);
|
||||||
write_with_helper(helper, 12);
|
write_with_helper(helper, 12);
|
||||||
@@ -52,7 +54,7 @@ TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]")
|
|||||||
TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]")
|
TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string target_filename = "test_logs/file_helper_test.txt";
|
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
size_t expected_size = 14;
|
size_t expected_size = 14;
|
||||||
file_helper helper;
|
file_helper helper;
|
||||||
helper.open(target_filename);
|
helper.open(target_filename);
|
||||||
@@ -62,16 +64,13 @@ TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]")
|
|||||||
REQUIRE(helper.size() == expected_size);
|
REQUIRE(helper.size() == expected_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_split_ext(const char *fname, const char *expect_base, const char *expect_ext)
|
static void test_split_ext(const spdlog::filename_t::value_type *fname, const spdlog::filename_t::value_type *expect_base,
|
||||||
|
const spdlog::filename_t::value_type *expect_ext)
|
||||||
{
|
{
|
||||||
spdlog::filename_t filename(fname);
|
spdlog::filename_t filename(fname);
|
||||||
spdlog::filename_t expected_base(expect_base);
|
spdlog::filename_t expected_base(expect_base);
|
||||||
spdlog::filename_t expected_ext(expect_ext);
|
spdlog::filename_t expected_ext(expect_ext);
|
||||||
|
|
||||||
#ifdef _WIN32 // replace folder sep
|
|
||||||
std::replace(filename.begin(), filename.end(), '/', '\\');
|
|
||||||
std::replace(expected_base.begin(), expected_base.end(), '/', '\\');
|
|
||||||
#endif
|
|
||||||
spdlog::filename_t basename;
|
spdlog::filename_t basename;
|
||||||
spdlog::filename_t ext;
|
spdlog::filename_t ext;
|
||||||
std::tie(basename, ext) = file_helper::split_by_extension(filename);
|
std::tie(basename, ext) = file_helper::split_by_extension(filename);
|
||||||
@@ -81,22 +80,22 @@ static void test_split_ext(const char *fname, const char *expect_base, const cha
|
|||||||
|
|
||||||
TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]]")
|
TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]]")
|
||||||
{
|
{
|
||||||
test_split_ext("mylog.txt", "mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("mylog.txt"), SPDLOG_FILENAME_T("mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext(".mylog.txt", ".mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T(".mylog.txt"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext(".mylog", ".mylog", "");
|
test_split_ext(SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("/aaa/bb.d/mylog", "/aaa/bb.d/mylog", "");
|
test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("/aaa/bb.d/mylog.txt", "/aaa/bb.d/mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog.txt"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("aaa/bbb/ccc/mylog.txt", "aaa/bbb/ccc/mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog.txt"), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("aaa/bbb/ccc/mylog.", "aaa/bbb/ccc/mylog.", "");
|
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("aaa/bbb/ccc/.mylog.txt", "aaa/bbb/ccc/.mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog.txt"), SPDLOG_FILENAME_T("aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("/aaa/bbb/ccc/mylog.txt", "/aaa/bbb/ccc/mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog.txt"), SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("/aaa/bbb/ccc/.mylog", "/aaa/bbb/ccc/.mylog", "");
|
test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("../mylog.txt", "../mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("../mylog.txt"), SPDLOG_FILENAME_T("../mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext(".././mylog.txt", ".././mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt"), SPDLOG_FILENAME_T(".././mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext(".././mylog.txt/xxx", ".././mylog.txt/xxx", "");
|
test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("/mylog.txt", "/mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("/mylog.txt"), SPDLOG_FILENAME_T("/mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("//mylog.txt", "//mylog", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("//mylog.txt"), SPDLOG_FILENAME_T("//mylog"), SPDLOG_FILENAME_T(".txt"));
|
||||||
test_split_ext("", "", "");
|
test_split_ext(SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext(".", ".", "");
|
test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(""));
|
||||||
test_split_ext("..txt", ".", ".txt");
|
test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
*/
|
*/
|
||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
|
#define SIMPLE_LOG "test_logs/simple_log"
|
||||||
|
#define ROTATING_LOG "test_logs/rotating_log"
|
||||||
|
|
||||||
TEST_CASE("simple_file_logger", "[simple_logger]]")
|
TEST_CASE("simple_file_logger", "[simple_logger]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string filename = "test_logs/simple_log";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
|
||||||
|
|
||||||
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
||||||
logger->set_pattern("%v");
|
logger->set_pattern("%v");
|
||||||
@@ -15,29 +18,29 @@ TEST_CASE("simple_file_logger", "[simple_logger]]")
|
|||||||
logger->info("Test message {}", 2);
|
logger->info("Test message {}", 2);
|
||||||
|
|
||||||
logger->flush();
|
logger->flush();
|
||||||
require_message_count(filename, 2);
|
require_message_count(SIMPLE_LOG, 2);
|
||||||
using spdlog::details::os::default_eol;
|
using spdlog::details::os::default_eol;
|
||||||
REQUIRE(file_contents(filename) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol));
|
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("flush_on", "[flush_on]]")
|
TEST_CASE("flush_on", "[flush_on]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string filename = "test_logs/simple_log";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG);
|
||||||
|
|
||||||
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
||||||
logger->set_pattern("%v");
|
logger->set_pattern("%v");
|
||||||
logger->set_level(spdlog::level::trace);
|
logger->set_level(spdlog::level::trace);
|
||||||
logger->flush_on(spdlog::level::info);
|
logger->flush_on(spdlog::level::info);
|
||||||
logger->trace("Should not be flushed");
|
logger->trace("Should not be flushed");
|
||||||
REQUIRE(count_lines(filename) == 0);
|
REQUIRE(count_lines(SIMPLE_LOG) == 0);
|
||||||
|
|
||||||
logger->info("Test message {}", 1);
|
logger->info("Test message {}", 1);
|
||||||
logger->info("Test message {}", 2);
|
logger->info("Test message {}", 2);
|
||||||
|
|
||||||
require_message_count(filename, 3);
|
require_message_count(SIMPLE_LOG, 3);
|
||||||
using spdlog::details::os::default_eol;
|
using spdlog::details::os::default_eol;
|
||||||
REQUIRE(file_contents(filename) ==
|
REQUIRE(file_contents(SIMPLE_LOG) ==
|
||||||
fmt::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol));
|
fmt::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
|
|||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
size_t max_size = 1024 * 10;
|
size_t max_size = 1024 * 10;
|
||||||
std::string basename = "test_logs/rotating_log";
|
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
|
||||||
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0);
|
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0);
|
||||||
|
|
||||||
for (int i = 0; i < 10; ++i)
|
for (int i = 0; i < 10; ++i)
|
||||||
@@ -54,14 +57,14 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger->flush();
|
logger->flush();
|
||||||
require_message_count(basename, 10);
|
require_message_count(ROTATING_LOG, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
|
TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
|
||||||
{
|
{
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
size_t max_size = 1024 * 10;
|
size_t max_size = 1024 * 10;
|
||||||
std::string basename = "test_logs/rotating_log";
|
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
|
||||||
|
|
||||||
{
|
{
|
||||||
// make an initial logger to create the first output file
|
// make an initial logger to create the first output file
|
||||||
@@ -83,7 +86,7 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
|
|||||||
|
|
||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
require_message_count(basename, 10);
|
require_message_count(ROTATING_LOG, 10);
|
||||||
|
|
||||||
for (int i = 0; i < 1000; i++)
|
for (int i = 0; i < 1000; i++)
|
||||||
{
|
{
|
||||||
@@ -92,7 +95,6 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger->flush();
|
logger->flush();
|
||||||
REQUIRE(get_filesize(basename) <= max_size);
|
REQUIRE(get_filesize(ROTATING_LOG) <= max_size);
|
||||||
auto filename1 = basename + ".1";
|
REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size);
|
||||||
REQUIRE(get_filesize(filename1) <= max_size);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,13 @@
|
|||||||
# error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG"
|
# error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define TEST_FILENAME "test_logs/simple_log"
|
||||||
|
|
||||||
TEST_CASE("debug and trace w/o format string", "[macros]]")
|
TEST_CASE("debug and trace w/o format string", "[macros]]")
|
||||||
{
|
{
|
||||||
|
|
||||||
prepare_logdir();
|
prepare_logdir();
|
||||||
std::string filename = "test_logs/simple_log";
|
spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME);
|
||||||
|
|
||||||
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename);
|
||||||
logger->set_pattern("%v");
|
logger->set_pattern("%v");
|
||||||
@@ -23,8 +25,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
|
|||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
using spdlog::details::os::default_eol;
|
using spdlog::details::os::default_eol;
|
||||||
REQUIRE(ends_with(file_contents(filename), fmt::format("Test message 2{}", default_eol)));
|
REQUIRE(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 2{}", default_eol)));
|
||||||
REQUIRE(count_lines(filename) == 1);
|
REQUIRE(count_lines(TEST_FILENAME) == 1);
|
||||||
|
|
||||||
spdlog::set_default_logger(logger);
|
spdlog::set_default_logger(logger);
|
||||||
|
|
||||||
@@ -32,8 +34,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
|
|||||||
SPDLOG_DEBUG("Test message {}", 4);
|
SPDLOG_DEBUG("Test message {}", 4);
|
||||||
logger->flush();
|
logger->flush();
|
||||||
|
|
||||||
require_message_count(filename, 2);
|
require_message_count(TEST_FILENAME, 2);
|
||||||
REQUIRE(ends_with(file_contents(filename), fmt::format("Test message 4{}", default_eol)));
|
REQUIRE(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 4{}", default_eol)));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("disable param evaluation", "[macros]")
|
TEST_CASE("disable param evaluation", "[macros]")
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ TEST_CASE("color range test1", "[pattern_formatter]")
|
|||||||
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
|
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
|
||||||
|
|
||||||
memory_buf_t buf;
|
memory_buf_t buf;
|
||||||
fmt::format_to(buf, "Hello");
|
fmt::format_to(std::back_inserter(buf), "Hello");
|
||||||
memory_buf_t formatted;
|
memory_buf_t formatted;
|
||||||
std::string logger_name = "test";
|
std::string logger_name = "test";
|
||||||
spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
|
spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
|
||||||
@@ -227,7 +227,7 @@ TEST_CASE("paddinng_truncate", "[pattern_formatter]")
|
|||||||
REQUIRE(log_to_str("123456", "%0!v", spdlog::pattern_time_type::local, "\n") == "\n");
|
REQUIRE(log_to_str("123456", "%0!v", spdlog::pattern_time_type::local, "\n") == "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("paddinng_truncate_funcname", "[pattern_formatter]")
|
TEST_CASE("padding_truncate_funcname", "[pattern_formatter]")
|
||||||
{
|
{
|
||||||
spdlog::sinks::test_sink_st test_sink;
|
spdlog::sinks::test_sink_st test_sink;
|
||||||
|
|
||||||
@@ -237,13 +237,28 @@ TEST_CASE("paddinng_truncate_funcname", "[pattern_formatter]")
|
|||||||
|
|
||||||
spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", spdlog::level::info, "message"};
|
spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", spdlog::level::info, "message"};
|
||||||
test_sink.log(msg1);
|
test_sink.log(msg1);
|
||||||
|
REQUIRE(test_sink.lines()[0] == "message [ func]");
|
||||||
|
|
||||||
spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "function"}, "test_logger", spdlog::level::info, "message"};
|
spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "function"}, "test_logger", spdlog::level::info, "message"};
|
||||||
test_sink.log(msg2);
|
test_sink.log(msg2);
|
||||||
|
REQUIRE(test_sink.lines()[1] == "message [funct]");
|
||||||
|
}
|
||||||
|
|
||||||
auto lines = test_sink.lines();
|
TEST_CASE("padding_funcname", "[pattern_formatter]")
|
||||||
REQUIRE(lines[0] == "message [ func]");
|
{
|
||||||
REQUIRE(lines[1] == "message [funct]");
|
spdlog::sinks::test_sink_st test_sink;
|
||||||
|
|
||||||
|
const char *pattern = "%v [%10!]";
|
||||||
|
auto formatter = std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(pattern));
|
||||||
|
test_sink.set_formatter(std::move(formatter));
|
||||||
|
|
||||||
|
spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", spdlog::level::info, "message"};
|
||||||
|
test_sink.log(msg1);
|
||||||
|
REQUIRE(test_sink.lines()[0] == "message [ func]");
|
||||||
|
|
||||||
|
spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "func567890123"}, "test_logger", spdlog::level::info, "message"};
|
||||||
|
test_sink.log(msg2);
|
||||||
|
REQUIRE(test_sink.lines()[1] == "message [func567890123]");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("clone-default-formatter", "[pattern_formatter]")
|
TEST_CASE("clone-default-formatter", "[pattern_formatter]")
|
||||||
@@ -357,7 +372,7 @@ TEST_CASE("clone-custom_formatter", "[pattern_formatter]")
|
|||||||
//
|
//
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
static const char *const test_path = "\\a\\b\\myfile.cpp";
|
static const char *const test_path = "\\a\\b\\c/myfile.cpp";
|
||||||
#else
|
#else
|
||||||
static const char *const test_path = "/a/b//myfile.cpp";
|
static const char *const test_path = "/a/b//myfile.cpp";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ TEST_CASE("disable automatic registration", "[registry]")
|
|||||||
spdlog::set_level(log_level);
|
spdlog::set_level(log_level);
|
||||||
// but disable automatic registration
|
// but disable automatic registration
|
||||||
spdlog::set_automatic_registration(false);
|
spdlog::set_automatic_registration(false);
|
||||||
auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>(tested_logger_name, "filename", 11, 59);
|
auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>(tested_logger_name, SPDLOG_FILENAME_T("filename"), 11, 59);
|
||||||
auto logger2 = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>(tested_logger_name2);
|
auto logger2 = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>(tested_logger_name2);
|
||||||
// loggers should not be part of the registry
|
// loggers should not be part of the registry
|
||||||
REQUIRE_FALSE(spdlog::get(tested_logger_name));
|
REQUIRE_FALSE(spdlog::get(tested_logger_name));
|
||||||
|
|||||||
35
tests/test_stopwatch.cpp
Normal file
35
tests/test_stopwatch.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "includes.h"
|
||||||
|
#include "test_sink.h"
|
||||||
|
#include "spdlog/stopwatch.h"
|
||||||
|
|
||||||
|
TEST_CASE("stopwatch1", "[stopwatch]")
|
||||||
|
{
|
||||||
|
using std::chrono::milliseconds;
|
||||||
|
milliseconds wait_ms(250);
|
||||||
|
milliseconds tolerance_ms(250);
|
||||||
|
|
||||||
|
spdlog::stopwatch sw;
|
||||||
|
std::this_thread::sleep_for(wait_ms);
|
||||||
|
REQUIRE(sw.elapsed() >= wait_ms);
|
||||||
|
REQUIRE(sw.elapsed() <= wait_ms + tolerance_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("stopwatch2", "[stopwatch]")
|
||||||
|
{
|
||||||
|
using spdlog::sinks::test_sink_st;
|
||||||
|
|
||||||
|
std::chrono::duration<double> wait_duration(0.250);
|
||||||
|
std::chrono::duration<double> tolerance_duration(0.250);
|
||||||
|
|
||||||
|
auto test_sink = std::make_shared<test_sink_st>();
|
||||||
|
|
||||||
|
spdlog::stopwatch sw;
|
||||||
|
spdlog::logger logger("test-stopwatch", test_sink);
|
||||||
|
logger.set_pattern("%v");
|
||||||
|
std::this_thread::sleep_for(wait_duration);
|
||||||
|
logger.info("{}", sw);
|
||||||
|
auto val = std::stod(test_sink->lines()[0]);
|
||||||
|
|
||||||
|
REQUIRE(val >= wait_duration.count());
|
||||||
|
REQUIRE(val <= (wait_duration + tolerance_duration).count());
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "includes.h"
|
#include "includes.h"
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifdef _WIN32
|
||||||
|
# include <Windows.h>
|
||||||
|
#else
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
# include <dirent.h>
|
# include <dirent.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -82,7 +84,7 @@ bool ends_with(std::string const &value, std::string const &ending)
|
|||||||
std::size_t count_files(const std::string &folder)
|
std::size_t count_files(const std::string &folder)
|
||||||
{
|
{
|
||||||
size_t counter = 0;
|
size_t counter = 0;
|
||||||
WIN32_FIND_DATA ffd;
|
WIN32_FIND_DATAA ffd;
|
||||||
|
|
||||||
// Start iterating over the files in the folder directory.
|
// Start iterating over the files in the folder directory.
|
||||||
HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &ffd);
|
HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &ffd);
|
||||||
@@ -92,7 +94,7 @@ std::size_t count_files(const std::string &folder)
|
|||||||
{
|
{
|
||||||
if (ffd.cFileName[0] != '.')
|
if (ffd.cFileName[0] != '.')
|
||||||
counter++;
|
counter++;
|
||||||
} while (::FindNextFile(hFind, &ffd) != 0);
|
} while (::FindNextFileA(hFind, &ffd) != 0);
|
||||||
::FindClose(hFind);
|
::FindClose(hFind);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user