From 25447b0f34b7b2b3b89374e4136d947179811624 Mon Sep 17 00:00:00 2001 From: ternaryop8479 Date: Sat, 14 Feb 2026 13:36:49 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=B0=86spdlog=E5=92=8Cstb=E4=BD=9C?= =?UTF-8?q?=E4=B8=BA=E6=99=AE=E9=80=9A=E7=9B=AE=E5=BD=95=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/cornell_box | Bin 437712 -> 413248 bytes lib/spdlog | 1 - lib/spdlog/.clang-tidy | 53 + lib/spdlog/CMakeLists.txt | 413 ++ lib/spdlog/INSTALL | 27 + lib/spdlog/LICENSE | 25 + lib/spdlog/README.md | 553 +++ lib/spdlog/appveyor.yml | 89 + lib/spdlog/bench/CMakeLists.txt | 37 + lib/spdlog/bench/async_bench.cpp | 168 + lib/spdlog/bench/bench.cpp | 246 + lib/spdlog/bench/formatter-bench.cpp | 71 + lib/spdlog/bench/latency.cpp | 220 + lib/spdlog/bench/utils.h | 32 + lib/spdlog/cmake/ide.cmake | 18 + lib/spdlog/cmake/pch.h.in | 254 + lib/spdlog/cmake/spdlog.pc.in | 13 + lib/spdlog/cmake/spdlogCPack.cmake | 60 + lib/spdlog/cmake/spdlogConfig.cmake.in | 20 + lib/spdlog/cmake/utils.cmake | 73 + lib/spdlog/cmake/version.rc.in | 42 + lib/spdlog/example/CMakeLists.txt | 23 + lib/spdlog/example/example.cpp | 401 ++ lib/spdlog/include/spdlog/async.h | 99 + lib/spdlog/include/spdlog/async_logger-inl.h | 84 + lib/spdlog/include/spdlog/async_logger.h | 74 + lib/spdlog/include/spdlog/cfg/argv.h | 40 + lib/spdlog/include/spdlog/cfg/env.h | 36 + lib/spdlog/include/spdlog/cfg/helpers-inl.h | 106 + lib/spdlog/include/spdlog/cfg/helpers.h | 29 + lib/spdlog/include/spdlog/common-inl.h | 68 + lib/spdlog/include/spdlog/common.h | 406 ++ .../include/spdlog/details/backtracer-inl.h | 63 + .../include/spdlog/details/backtracer.h | 45 + .../include/spdlog/details/circular_q.h | 115 + .../include/spdlog/details/console_globals.h | 28 + .../include/spdlog/details/file_helper-inl.h | 151 + .../include/spdlog/details/file_helper.h | 61 + .../include/spdlog/details/fmt_helper.h | 141 + .../include/spdlog/details/log_msg-inl.h | 44 + lib/spdlog/include/spdlog/details/log_msg.h | 40 + .../spdlog/details/log_msg_buffer-inl.h | 54 + .../include/spdlog/details/log_msg_buffer.h | 32 + .../include/spdlog/details/mpmc_blocking_q.h | 177 + .../include/spdlog/details/null_mutex.h | 35 + lib/spdlog/include/spdlog/details/os-inl.h | 570 +++ lib/spdlog/include/spdlog/details/os.h | 127 + .../spdlog/details/periodic_worker-inl.h | 26 + .../include/spdlog/details/periodic_worker.h | 58 + .../include/spdlog/details/registry-inl.h | 270 + lib/spdlog/include/spdlog/details/registry.h | 131 + .../spdlog/details/synchronous_factory.h | 22 + .../spdlog/details/tcp_client-windows.h | 217 + .../include/spdlog/details/tcp_client.h | 203 + .../include/spdlog/details/thread_pool-inl.h | 125 + .../include/spdlog/details/thread_pool.h | 117 + .../spdlog/details/udp_client-windows.h | 98 + .../include/spdlog/details/udp_client.h | 80 + .../include/spdlog/details/windows_include.h | 11 + lib/spdlog/include/spdlog/fmt/bin_to_hex.h | 224 + lib/spdlog/include/spdlog/fmt/bundled/args.h | 220 + lib/spdlog/include/spdlog/fmt/bundled/base.h | 3010 +++++++++++ .../include/spdlog/fmt/bundled/chrono.h | 2246 +++++++++ lib/spdlog/include/spdlog/fmt/bundled/color.h | 637 +++ .../include/spdlog/fmt/bundled/compile.h | 588 +++ lib/spdlog/include/spdlog/fmt/bundled/core.h | 5 + .../spdlog/fmt/bundled/fmt.license.rst | 27 + .../include/spdlog/fmt/bundled/format-inl.h | 1948 ++++++++ .../include/spdlog/fmt/bundled/format.h | 4395 +++++++++++++++++ lib/spdlog/include/spdlog/fmt/bundled/os.h | 427 ++ .../include/spdlog/fmt/bundled/ostream.h | 167 + .../include/spdlog/fmt/bundled/printf.h | 624 +++ .../include/spdlog/fmt/bundled/ranges.h | 851 ++++ lib/spdlog/include/spdlog/fmt/bundled/std.h | 727 +++ lib/spdlog/include/spdlog/fmt/bundled/xchar.h | 356 ++ lib/spdlog/include/spdlog/fmt/chrono.h | 23 + lib/spdlog/include/spdlog/fmt/compile.h | 23 + lib/spdlog/include/spdlog/fmt/fmt.h | 26 + lib/spdlog/include/spdlog/fmt/ostr.h | 23 + lib/spdlog/include/spdlog/fmt/ranges.h | 23 + lib/spdlog/include/spdlog/fmt/std.h | 24 + lib/spdlog/include/spdlog/fmt/xchar.h | 23 + lib/spdlog/include/spdlog/formatter.h | 17 + lib/spdlog/include/spdlog/fwd.h | 18 + lib/spdlog/include/spdlog/logger-inl.h | 197 + lib/spdlog/include/spdlog/logger.h | 379 ++ lib/spdlog/include/spdlog/mdc.h | 52 + .../include/spdlog/pattern_formatter-inl.h | 1350 +++++ lib/spdlog/include/spdlog/pattern_formatter.h | 118 + .../include/spdlog/sinks/android_sink.h | 137 + .../include/spdlog/sinks/ansicolor_sink-inl.h | 142 + .../include/spdlog/sinks/ansicolor_sink.h | 118 + .../include/spdlog/sinks/base_sink-inl.h | 59 + lib/spdlog/include/spdlog/sinks/base_sink.h | 51 + .../spdlog/sinks/basic_file_sink-inl.h | 48 + .../include/spdlog/sinks/basic_file_sink.h | 66 + .../include/spdlog/sinks/callback_sink.h | 56 + .../include/spdlog/sinks/daily_file_sink.h | 254 + lib/spdlog/include/spdlog/sinks/dist_sink.h | 81 + .../include/spdlog/sinks/dup_filter_sink.h | 91 + .../include/spdlog/sinks/hourly_file_sink.h | 193 + lib/spdlog/include/spdlog/sinks/kafka_sink.h | 119 + lib/spdlog/include/spdlog/sinks/mongo_sink.h | 108 + lib/spdlog/include/spdlog/sinks/msvc_sink.h | 68 + lib/spdlog/include/spdlog/sinks/null_sink.h | 41 + .../include/spdlog/sinks/ostream_sink.h | 43 + lib/spdlog/include/spdlog/sinks/qt_sinks.h | 308 ++ .../include/spdlog/sinks/ringbuffer_sink.h | 71 + .../spdlog/sinks/rotating_file_sink-inl.h | 179 + .../include/spdlog/sinks/rotating_file_sink.h | 93 + lib/spdlog/include/spdlog/sinks/sink-inl.h | 22 + lib/spdlog/include/spdlog/sinks/sink.h | 34 + .../spdlog/sinks/stdout_color_sinks-inl.h | 38 + .../include/spdlog/sinks/stdout_color_sinks.h | 49 + .../include/spdlog/sinks/stdout_sinks-inl.h | 127 + .../include/spdlog/sinks/stdout_sinks.h | 84 + lib/spdlog/include/spdlog/sinks/syslog_sink.h | 104 + .../include/spdlog/sinks/systemd_sink.h | 121 + lib/spdlog/include/spdlog/sinks/tcp_sink.h | 89 + lib/spdlog/include/spdlog/sinks/udp_sink.h | 69 + .../include/spdlog/sinks/win_eventlog_sink.h | 260 + .../include/spdlog/sinks/wincolor_sink-inl.h | 172 + .../include/spdlog/sinks/wincolor_sink.h | 82 + lib/spdlog/include/spdlog/spdlog-inl.h | 96 + lib/spdlog/include/spdlog/spdlog.h | 354 ++ lib/spdlog/include/spdlog/stopwatch.h | 66 + lib/spdlog/include/spdlog/tweakme.h | 148 + lib/spdlog/include/spdlog/version.h | 11 + lib/spdlog/logos/jetbrains-variant-4.svg | 43 + lib/spdlog/scripts/ci_setup_clang.sh | 12 + lib/spdlog/scripts/extract_version.py | 17 + lib/spdlog/scripts/format.sh | 19 + lib/spdlog/src/async.cpp | 11 + lib/spdlog/src/bundled_fmtlib_format.cpp | 48 + lib/spdlog/src/cfg.cpp | 8 + lib/spdlog/src/color_sinks.cpp | 55 + lib/spdlog/src/file_sinks.cpp | 20 + lib/spdlog/src/spdlog.cpp | 28 + lib/spdlog/src/stdout_sinks.cpp | 37 + lib/spdlog/tests/CMakeLists.txt | 92 + lib/spdlog/tests/includes.h | 45 + lib/spdlog/tests/main.cpp | 10 + lib/spdlog/tests/test_async.cpp | 200 + lib/spdlog/tests/test_backtrace.cpp | 73 + lib/spdlog/tests/test_bin_to_hex.cpp | 97 + lib/spdlog/tests/test_cfg.cpp | 178 + lib/spdlog/tests/test_circular_q.cpp | 50 + lib/spdlog/tests/test_create_dir.cpp | 144 + lib/spdlog/tests/test_custom_callbacks.cpp | 37 + lib/spdlog/tests/test_daily_logger.cpp | 169 + lib/spdlog/tests/test_dup_filter.cpp | 83 + lib/spdlog/tests/test_errors.cpp | 112 + lib/spdlog/tests/test_eventlog.cpp | 75 + lib/spdlog/tests/test_file_helper.cpp | 169 + lib/spdlog/tests/test_file_logging.cpp | 187 + lib/spdlog/tests/test_fmt_helper.cpp | 82 + lib/spdlog/tests/test_macros.cpp | 53 + lib/spdlog/tests/test_misc.cpp | 224 + lib/spdlog/tests/test_mpmc_q.cpp | 114 + lib/spdlog/tests/test_pattern_formatter.cpp | 667 +++ lib/spdlog/tests/test_registry.cpp | 125 + lib/spdlog/tests/test_ringbuffer.cpp | 52 + lib/spdlog/tests/test_sink.h | 70 + lib/spdlog/tests/test_stdout_api.cpp | 96 + lib/spdlog/tests/test_stopwatch.cpp | 42 + lib/spdlog/tests/test_systemd.cpp | 14 + lib/spdlog/tests/test_time_point.cpp | 35 + lib/spdlog/tests/test_timezone.cpp | 146 + lib/spdlog/tests/utils.cpp | 102 + lib/spdlog/tests/utils.h | 18 + 170 files changed, 33680 insertions(+), 1 deletion(-) delete mode 160000 lib/spdlog create mode 100644 lib/spdlog/.clang-tidy create mode 100644 lib/spdlog/CMakeLists.txt create mode 100644 lib/spdlog/INSTALL create mode 100644 lib/spdlog/LICENSE create mode 100644 lib/spdlog/README.md create mode 100644 lib/spdlog/appveyor.yml create mode 100644 lib/spdlog/bench/CMakeLists.txt create mode 100644 lib/spdlog/bench/async_bench.cpp create mode 100644 lib/spdlog/bench/bench.cpp create mode 100644 lib/spdlog/bench/formatter-bench.cpp create mode 100644 lib/spdlog/bench/latency.cpp create mode 100644 lib/spdlog/bench/utils.h create mode 100644 lib/spdlog/cmake/ide.cmake create mode 100644 lib/spdlog/cmake/pch.h.in create mode 100644 lib/spdlog/cmake/spdlog.pc.in create mode 100644 lib/spdlog/cmake/spdlogCPack.cmake create mode 100644 lib/spdlog/cmake/spdlogConfig.cmake.in create mode 100644 lib/spdlog/cmake/utils.cmake create mode 100644 lib/spdlog/cmake/version.rc.in create mode 100644 lib/spdlog/example/CMakeLists.txt create mode 100644 lib/spdlog/example/example.cpp create mode 100644 lib/spdlog/include/spdlog/async.h create mode 100644 lib/spdlog/include/spdlog/async_logger-inl.h create mode 100644 lib/spdlog/include/spdlog/async_logger.h create mode 100644 lib/spdlog/include/spdlog/cfg/argv.h create mode 100644 lib/spdlog/include/spdlog/cfg/env.h create mode 100644 lib/spdlog/include/spdlog/cfg/helpers-inl.h create mode 100644 lib/spdlog/include/spdlog/cfg/helpers.h create mode 100644 lib/spdlog/include/spdlog/common-inl.h create mode 100644 lib/spdlog/include/spdlog/common.h create mode 100644 lib/spdlog/include/spdlog/details/backtracer-inl.h create mode 100644 lib/spdlog/include/spdlog/details/backtracer.h create mode 100644 lib/spdlog/include/spdlog/details/circular_q.h create mode 100644 lib/spdlog/include/spdlog/details/console_globals.h create mode 100644 lib/spdlog/include/spdlog/details/file_helper-inl.h create mode 100644 lib/spdlog/include/spdlog/details/file_helper.h create mode 100644 lib/spdlog/include/spdlog/details/fmt_helper.h create mode 100644 lib/spdlog/include/spdlog/details/log_msg-inl.h create mode 100644 lib/spdlog/include/spdlog/details/log_msg.h create mode 100644 lib/spdlog/include/spdlog/details/log_msg_buffer-inl.h create mode 100644 lib/spdlog/include/spdlog/details/log_msg_buffer.h create mode 100644 lib/spdlog/include/spdlog/details/mpmc_blocking_q.h create mode 100644 lib/spdlog/include/spdlog/details/null_mutex.h create mode 100644 lib/spdlog/include/spdlog/details/os-inl.h create mode 100644 lib/spdlog/include/spdlog/details/os.h create mode 100644 lib/spdlog/include/spdlog/details/periodic_worker-inl.h create mode 100644 lib/spdlog/include/spdlog/details/periodic_worker.h create mode 100644 lib/spdlog/include/spdlog/details/registry-inl.h create mode 100644 lib/spdlog/include/spdlog/details/registry.h create mode 100644 lib/spdlog/include/spdlog/details/synchronous_factory.h create mode 100644 lib/spdlog/include/spdlog/details/tcp_client-windows.h create mode 100644 lib/spdlog/include/spdlog/details/tcp_client.h create mode 100644 lib/spdlog/include/spdlog/details/thread_pool-inl.h create mode 100644 lib/spdlog/include/spdlog/details/thread_pool.h create mode 100644 lib/spdlog/include/spdlog/details/udp_client-windows.h create mode 100644 lib/spdlog/include/spdlog/details/udp_client.h create mode 100644 lib/spdlog/include/spdlog/details/windows_include.h create mode 100644 lib/spdlog/include/spdlog/fmt/bin_to_hex.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/args.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/base.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/chrono.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/color.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/compile.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/core.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/fmt.license.rst create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/format-inl.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/format.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/os.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/ostream.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/printf.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/ranges.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/std.h create mode 100644 lib/spdlog/include/spdlog/fmt/bundled/xchar.h create mode 100644 lib/spdlog/include/spdlog/fmt/chrono.h create mode 100644 lib/spdlog/include/spdlog/fmt/compile.h create mode 100644 lib/spdlog/include/spdlog/fmt/fmt.h create mode 100644 lib/spdlog/include/spdlog/fmt/ostr.h create mode 100644 lib/spdlog/include/spdlog/fmt/ranges.h create mode 100644 lib/spdlog/include/spdlog/fmt/std.h create mode 100644 lib/spdlog/include/spdlog/fmt/xchar.h create mode 100644 lib/spdlog/include/spdlog/formatter.h create mode 100644 lib/spdlog/include/spdlog/fwd.h create mode 100644 lib/spdlog/include/spdlog/logger-inl.h create mode 100644 lib/spdlog/include/spdlog/logger.h create mode 100644 lib/spdlog/include/spdlog/mdc.h create mode 100644 lib/spdlog/include/spdlog/pattern_formatter-inl.h create mode 100644 lib/spdlog/include/spdlog/pattern_formatter.h create mode 100644 lib/spdlog/include/spdlog/sinks/android_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/ansicolor_sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/ansicolor_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/base_sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/base_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/basic_file_sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/basic_file_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/callback_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/daily_file_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/dist_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/dup_filter_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/hourly_file_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/kafka_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/mongo_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/msvc_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/null_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/ostream_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/qt_sinks.h create mode 100644 lib/spdlog/include/spdlog/sinks/ringbuffer_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/rotating_file_sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/rotating_file_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/stdout_color_sinks-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/stdout_color_sinks.h create mode 100644 lib/spdlog/include/spdlog/sinks/stdout_sinks-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/stdout_sinks.h create mode 100644 lib/spdlog/include/spdlog/sinks/syslog_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/systemd_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/tcp_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/udp_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/win_eventlog_sink.h create mode 100644 lib/spdlog/include/spdlog/sinks/wincolor_sink-inl.h create mode 100644 lib/spdlog/include/spdlog/sinks/wincolor_sink.h create mode 100644 lib/spdlog/include/spdlog/spdlog-inl.h create mode 100644 lib/spdlog/include/spdlog/spdlog.h create mode 100644 lib/spdlog/include/spdlog/stopwatch.h create mode 100644 lib/spdlog/include/spdlog/tweakme.h create mode 100644 lib/spdlog/include/spdlog/version.h create mode 100644 lib/spdlog/logos/jetbrains-variant-4.svg create mode 100644 lib/spdlog/scripts/ci_setup_clang.sh create mode 100644 lib/spdlog/scripts/extract_version.py create mode 100644 lib/spdlog/scripts/format.sh create mode 100644 lib/spdlog/src/async.cpp create mode 100644 lib/spdlog/src/bundled_fmtlib_format.cpp create mode 100644 lib/spdlog/src/cfg.cpp create mode 100644 lib/spdlog/src/color_sinks.cpp create mode 100644 lib/spdlog/src/file_sinks.cpp create mode 100644 lib/spdlog/src/spdlog.cpp create mode 100644 lib/spdlog/src/stdout_sinks.cpp create mode 100644 lib/spdlog/tests/CMakeLists.txt create mode 100644 lib/spdlog/tests/includes.h create mode 100644 lib/spdlog/tests/main.cpp create mode 100644 lib/spdlog/tests/test_async.cpp create mode 100644 lib/spdlog/tests/test_backtrace.cpp create mode 100644 lib/spdlog/tests/test_bin_to_hex.cpp create mode 100644 lib/spdlog/tests/test_cfg.cpp create mode 100644 lib/spdlog/tests/test_circular_q.cpp create mode 100644 lib/spdlog/tests/test_create_dir.cpp create mode 100644 lib/spdlog/tests/test_custom_callbacks.cpp create mode 100644 lib/spdlog/tests/test_daily_logger.cpp create mode 100644 lib/spdlog/tests/test_dup_filter.cpp create mode 100644 lib/spdlog/tests/test_errors.cpp create mode 100644 lib/spdlog/tests/test_eventlog.cpp create mode 100644 lib/spdlog/tests/test_file_helper.cpp create mode 100644 lib/spdlog/tests/test_file_logging.cpp create mode 100644 lib/spdlog/tests/test_fmt_helper.cpp create mode 100644 lib/spdlog/tests/test_macros.cpp create mode 100644 lib/spdlog/tests/test_misc.cpp create mode 100644 lib/spdlog/tests/test_mpmc_q.cpp create mode 100644 lib/spdlog/tests/test_pattern_formatter.cpp create mode 100644 lib/spdlog/tests/test_registry.cpp create mode 100644 lib/spdlog/tests/test_ringbuffer.cpp create mode 100644 lib/spdlog/tests/test_sink.h create mode 100644 lib/spdlog/tests/test_stdout_api.cpp create mode 100644 lib/spdlog/tests/test_stopwatch.cpp create mode 100644 lib/spdlog/tests/test_systemd.cpp create mode 100644 lib/spdlog/tests/test_time_point.cpp create mode 100644 lib/spdlog/tests/test_timezone.cpp create mode 100644 lib/spdlog/tests/utils.cpp create mode 100644 lib/spdlog/tests/utils.h diff --git a/examples/cornell_box b/examples/cornell_box index 9106b6a9a14dae7cd3f5f5a76efe9a6ffdc2d9a8..581f443857a4aa28bc55ac089b31987b36160e1a 100644 GIT binary patch literal 413248 zcmeEvc|es_`u}?Y6-|Ayw5Y78u#s#5my$_~s~|6VZ5WqwstFN6aRCM{ie-vjNZxL@ zm{zB0W}If4ar#zTQ)-I^xS+P-lG!S`o+}xssYnXH&*!}Fh4*gF^80sgao+PR=Q+=L z&U2o#yysq*q&db#*ldxC`H56+Rcy#NK4KtJOngmRcQdCbDauGiMS7KTr9x~J{!&b8 z;8Qat`<~GzAJHuLok^3M$Z<%j*BgL@n0%712-H_Jr;WVy1h4gRvD{?9}* z;NyGx>`5;ERhjk-e5QcLPl`+xC9vEUvs~aa-sJZ^SqDFS@~`nz0~7d+1C5_JnRfMA z55O!D&Mqc|DAAL0ajwwn+xl%)%k8V?J67C5cKM^MEqm#Ek z{@9HyzxDek2gi=QqB!Z1l(bDH1rOYid#UTjx%ty?8h+#48Q0IvFDNa$zHH=8*WWb! zhLXY?hARC~?tc8yz1T-eU7}-UbhitX8G|F0>-*#r#SeM-c>sS8;%^B7e9G~+6n`%K z@$)eLbeU5WGn6Q=9i1`x{x_nI4Vu?`Y~M4dw$FL`jqCq3uxQ$CKPe3U`7q(wRqt(K_|-6aE)T<>8wMW{#(&m@;r}*Ff2|1Px5L87pBN_oTos0YR~UW% z7N%cThq2EMVf5b;#{S=jk-s(!K0l0{zl72M@-T7ZK$v(H0emRG`em4SI6F*x9bxo2 z9Hw2r3e&DWVfxV(M$Us_>b)gQ{P`#he_j|r`F$9BmWGkDFpQoX!?f$&F#H#Ui6^gy zvD?Wo?Y%0Dp8dkKcX1ef>chyN6lPrfE{vSyF!AlCFzwwNM$XwV{c>}d_7;WFGc!z_ zc`eL*dL)egT))TRFYxn782!t`_`_^$8bZbGUxXQVT*!vv-xH=@bbClWuM2~p6vj_} z7RJu6hlv}Nu>X}xtfDo+fcTWl6uN_-_&BeD&ouEXWn8*Ui92cFW2iVjN6|0jpT>^B z$H%`Lb|Ze|cLu&(QWfPn_$~28rhFg%5c-|?Sd%}_grGjA>_xm7lp`#+QWoZeu*=;c=Cj?a|;V{CudKen`_WQQfAK0TacdX%q%XeQ=(DNoe-E`Sl|Q4=Ps1ljDq}5mdW|^4AG%0G_YE} zDKoQh?%cHbsKL;4Y;kV3GuPmelG%_9Xi_{sd#*%G&MlsoUjQZvrRJ76iwhT83;0?( zIp0ak-F?R-=S_19bF$~=rY5D$2V#i@1v8zqvT|vH%<(y(PM(Yc zH)mz#l$9kVB_&VKF3HcyDnu>$1v8Q6X3xvWndD4L%9)j2oaHRe&Ucn%NE?weH#fUD4V0l-8HFYMbF!~s*E@k6ZWts#FT5pbLvW5P zEy&3(ojJ>ym0OmRTjYe(vG1V&NYtGLIk~ArWp~~&X@)O1{y(=`(#?4o7g>4v#U;+H zf>1RXp zXN*9X=FgCX$&>&6hTk#C*JERo(k4zSG+JzmC2IVn|I#~dMrgnw%1vW~CTHA{gdqSI z&z_z?KPhQMR#skNaZc`xtkQx7Fo)^KNyCip%FCadJH0e7T!$l+bXs6Uj&osAF2=&h z8HHI3@@M20q;XK8+kAar_)~q4CYmF3xNn5!7hvS)&&bLyo>@8%&TNd*J7DdRPDGll zqEe?+EE%50(FsMf@)0kxorT3AHBK9bB59L`WlgmBQcmXhq0V`bJ!uRLES+^k=jb)W zhLmPD?%%MKIR;^h|8!)duO?&_=N8S)&O!fCOWBE*0ritcg^H+iX08)wjp!P5MabX< zbND(O6Oasald~o#W~EKKIcs8gtr4##jmVmul$Dldgf9Oe(`7`&&oEBZ6+Hce@5s&$ z_;+LPe-QwNbNr_n@e;)mw-{Uy^Xc_);oOq6Oqi*wUw?XVfb}M8({r**oPmLStS^vvI^a)5oZ+%JI8?zd zCz)TMd*&6+;4Fi=J;^seWZ{s#IJ6XlHjk|N)dgpmtoG(E%g+@Fk=UH#CxN3iw-`oIwOFMJg?osdy)eC5X)idH(087hI4d7vM(CUEu4w@$Fe8+FB}z-0G?V{fSJ5B$C=@no`mhme7SUR zW*6isMTHCUl({o<%Zl=ptSNHUmo+iB1WPO>4@-p{r4(L=2PPyZwuM$N5y<$ZaB5l}es7cP{puxz1c{ zJFs_Tjc8HMydovfXeJ=^uS6Ctq{fm~lAAjRs=)*Wg;^BGjSHy-BttuH&iS5q7=d&L zvq{XBIA`Px8A9+Tg7Z3aGjm|CPPQBapFVbM*038Am2^kOn6X(yZy0)WSN4VxMt1pQrm@*@9(xl>w3hAsr0;M8ROLv?l8kk7s0x5;% zS;qxZhyLI-Y90XWg|ZxB>c`ULwJ8@NXSBj-n++{6bPh}@&1h>RPN9s_HYFa1srX-s z!(Zk#3vp6^KgtH|aqg0u&z9a$VRpnU|UbuY~^y{O%Xu`!3K2Lew zN0;S$Des%`&Q)`8A{niGY{I{h^zO>%CR{4ZcTa)XY6CWLbPkhCY6BB@M zF!|#G@QQhlYj6O5MIYn5I57ZUW8y~!;8&aYlmL8UUqg--fNwDQrv~5?O?{>X;Hzs5 zIkN)rD@=S*0KUP*mj&Rb)fsZi1Mo2$419F}UNiMs9)K@5<*x|9*O>T~0r(aZzd8V4 zZtAlx0AFP4vn2pO&D5tM0AFOv-xGjeZQ>gP@Qo&31mKHIeOdzWiivLxz#qNB@XH$0 zuYLZRGTp#81mIVg_&ovmbtWtV@Qo&aO8|b04>s-Lt2a*G#zTw>!29?|2H;ni{3!u= zAAe>5UNP_MO%1^N_-6&+rTzMUs8*F^T6LN8iWy{*id!ok!Aq@AZgZMuhqCy&ojL z(uc&;_uiBE2Q7HtdqCppSH6$$Jtpx>eMmfg?_r7e-N_``_kNRj`l0U=Yx*tmFd5vq6JT#e4mjPyf1c;mSVwU!P@!JEcjlX zSgeCB`136IsTMq5<#v9iS@2i@c7A4A@OUNM`6;sC@oK;GQ)a>YVh@Ss7Ce@Uou4WT z-g+;g+Je8JlNjTU^S z1)pfaPqpBaEcj^_{4fiCmIXiDf-kb*M_BM>7W_>Xe7OaGvjtye!E>MQ`&3)-$vz~W z%PshyTktC^_**Ral@|Oc3x2f)KiYy{XTjfU!Pi*uw^{I8Ecg@)zQKaGTkv}<_%RlI zqXj?Kf)^Hiss-O-!KYd9trq+^3tlpJl;MvEYj= z_&Y85G7J7L3%=ZfpK8HZS@68$>HAb$@OS%=crLf#@3G)lSn&5+@GC9&Ut932E%^H^ z_;nV1mIYs9!B4Z`w^;Dm7JP#RKiz`gW5Lg`;2SOYTnk=U@Oc(|iv>T^f^W6pXIb!y zxh~~eBj19Lw&3Sj@G%zrTnj$df}dx>$64@&7W`lfzQ}@4wBUbZ!H=}yi!JyR3%RpgRVAh*Z5I1v$cUCF5=Ez-Le$VIJXl8zlTT zVIJLhYb5+SVIJ9dS4;RM!d(1&S4j8;!aSn!R!jIf!aSPsmP_~v!dxbKizNIA;b_9s zB>WIz9>I7sC44_&9=&)|BwS3GM=st(3C||XqZV(Rgr^hc5sNoQ!gmwq(TZ1*@I=Bq zQt`I512O1!!aPdxHcEI5VIHA)8zlU5!aO?h)<}3LVIG-yS4;R>!aOSRu8{Cmgn8uZ zt(Nd5gn4A*Etl~5gn3lrEs}6A!aO4JPLps1VIIwTGbP;iDqtR!cvB>NoG_0_yonM% zLYPM*-Z%;GBg`WaZ;XV$Ak3o>uOi_cgn0zwZ8^vGZzCK>xKYA&gn8uQZIJNWgn88A zt&#BSgn7i_T`l342*(p%A>kJY^GL&6E#c<~UqiTD!cP$X8Q~%cKSFpg;b{_nh;Rbo zObOpl_*%j#5-ujpBMon&gl7}xQHD28!qW*~PdG-xcN6B3hF6jBM8Z7E@V1RoyhRf3MVLnu-f0q!Aj~5PZ>EIXUIEOb2ycpnj}zt*gf~&bM+o!i!5b&xeS~@B z;Ej>+7le7#;8i5NgD{U6ye&V<{wJJ5xKYA&gn6XkZIJNWgn5+Ut&#BSgn5MET`l34 z2&WQWA>kJY^T@zkE#c<~^QgdEF5xE#rxPxc@FRpZ!qX)D5aA5MnG(LAFpmPfDH1Lw zJf3i(gl7}xk$^W&!qW-!D8L&d;kya*2*9gIcp_nL{k<)1vi}Kl%kOQJ@EF3}>U$d` z{By$G;(Kc(Jd`lE_TJSJzLqez^xhQ`zKSrn^4@9*UqYB$cyGCc&nL{SySGTfy$Ey5 z?wuy#2*TW|dov~6_A+2@(Y+}WK2Dfhb8n)Aj}YdT+#4t1eT2Cc_r^&03&Pxjdld=q zAk3||x8;oNf5OuUH%hpUFt^&?1_{4Sm|JXbjf7t(oI`lEgkK`et+scCgkK=cEw;B> z!p{-r*4kSx;U@^sBwQrnM+kEQ@=lZRLxl4QXG-{f!m|mdNVu5r9Kwkbo=uorXm6Z^ z>sCf7dDVdbtB>aCV>=zGynP8~D9tk>>8(gSM&ALHu)%#m-ggS*n+@{$2Kfp+#6DOz z6ZA1GJ^>R3iXUGmi`z)AGwH>oUx^6<^c@EMJ3Pb-CjBAO@5O`&`s)V$@2vhI&>{W= z$=_l=1^G#Xyc!yb9D}@zGRUV``hY>c$skW8 zxw}FBjO3XH`67cn90J8*7#^yvBe}^Sx2}~f?*T5c1!Qq0d`%2wauE`6??B17*J2_? zL$(>*weN!aIYU?p$wLkD{~2VRqK|aaSmmB0&B=ZY+D1D5s5qnzJwDF{j{ckBNnh3K^66J=!b;^b3lf^ zv1y%nAtX~Av>8cl{(0J`$Qdwti_vK;_q3P|(1H3>H21ynnN1ajw!1<~yGYabiqAQ2 zWRq4=(qQ_)Ampmy1m|e!R}5P%G;H-8ZS^#2hpiUHCyM(`$I?Fc8Tt&RUD(AE859l}X3-;jc zOwvMfUJT>f@WWIN&&%=USYnGiPRSaZZV~-M7D&-N74g%+iGo|+hAZi*P%W0Fq;2j) zDfD@lJP9|8ad>)%)6nm-8}$uMUiyQ--u)r81VSC2H{xrkP0dLZaMSXTDI%mssqrJb zj2_LF_F_wOW&h~S;w#E``*95>*8!RjbXmVYy$|UdK^Iq`u(*U0JnWk4U|FcKSu7{- z2{gKzC1;ylQ@iwSCNqKnvkMo_YPsD z51V}Lcs9Mt5xIbix z8!2>&DfF&TENbOi#uczlrQm=5;Im^wDDG0JGjRh)YdTPvR+ec%mnAB8o}J3Df820}2>r6X~;yA>lm@&7?an1$O|G z9E!hUXQR5LU?I}+lc~m2C@dal@(9Jq@u&nBkmE6$1#-YvZPtBQbYmg^VJ_?bnudg; zZwAw47+4Rwhy_txKn`%agE^(7CrwFng6S~DUQt3xA0Ib_j0Jw)oP9O@dki!OW@j7d)Wi$3ajN_|;_J>gFqzG2 z0l_mm_f6#QHS+HepNTveH7O>c39aYk{q`}#abBSHJ)>`AscTpYQG5le1yd@m=bvb-FUMf$>PU=h7R7d^-Xd90s7 z$FEvLUWF;|-jFP^GqWl0U06=6W%9bTVY9e4WC0-NY>ihDE^uhHb+n&R?9vizE^S3pr)n+@yPL-NVs zG|m)tc}SLl6m>O4EruwukcpE?A(Poi-iw1NIKT7(K=-js#&Vz?G<$G;FkSZbCeTG3 z#a&7cNP04uQ_hsX2j_c5W_5r3KB&68sO)@-+YaJ;+{jZgXE3K!%wZ}PA6%Vun(HWO z848Psm^?@^5O*?IToNlj2LryxWjyOtf|%6GgP@Ud7Z}BjrnJ8YF_vCOrd8|?2x9&I zR#hwiW(sms(4nIS<9$JlrLvWIrl4Dc>G!gtaTM}r2zjr{)IF6FLv-XoQ_>H?bm=B% z*@t(5C?+zQz(iv*7Kw}DK(wqMn=R2IOLL`qD5RVL zG;I@2j!gvgtpQilyCT^v7Hi0ViP*+qEiTkNsn}F`M(=&qSm;NK8>vcL69mfnxJU}} zTWd$N$}&_bc7hE%D(;vxlg~0J7g3)~swO2}Lk^U@JeU*Xsaaej8?jfkv+f|TXcpZ~ z`ZvLJSiD*Kr?7)4MlrdW$xxHmvWgyz)*gEW^V>MDQICbjPWhCyYe5qyX^9pjn!bsO z=LQQ)WX1F70{UKXi7~+}uvW9U9W3HS%3DEs{&OGtXml29eM5i-Tg8bsa5cF_2_DkE zaY6)_lalElV!V$pMbwZF%C5tcOTo-ToSIYPXLU}Yr5Hn)AJ_}1D@8nJmUtM?rVmAj zX_l@6YUP8*Zt(4wp`=PB^-WFBF{uv_eI1(e6uG>I!R1%2S~<>;;v{`5=;EmmRb68+ z{enz?r4ZE8%TV+n+0nGGLlkS2k*ZmIYj!}F_6Sa%(F(;)3DT*Q9e&{dKue}RQ4lQf zA8TQ6Istpv;su6(OG+O=saIJlGFT#OdxEsXhl0wnw)v!e0GfCP6^Y+=)z&?zS*7FI zQ+?4Skx%9sOtP5V&14D^bM84TT7y-Q9Cy`&9QDX1u% z&)m;apBxBmrS#DINc(3njXpDmv}ZvR(^=cEyK39Pte?pYpZNm)B1Vuok;xDy*D$#f zNvF?j3~rqCnde#hG+TF^9L8XniIUQ1=plCl@UK^`bl`n0O?@9q%PI(D2UjtePMr)U z3hEams8mj$7gGf%RVb7aM#YOm$gs#a!6M~C<`67T`g=iiwXzSR3e>3skGRYfM1S&c zM5$a~{Q*p-jZTXdL0zI&&NZbuDUJQfYSV+INwt1qs`axVx>~tSMy$SmUw1=kh$dQ zwSeNbf+#jJc^?VwadWUt^!#3N8`w#|D3~r6cUMr{{ghP7q=?BJCV5C0ed5Gd%)#p| zGq-fA12i$JcsH0nkxoZ{7gvx7re}khq>w+ELKXzm(Om2gDddZNNIqrKfP`qxWVft; zWOFm6xMndpm@XYXk>cEx^e`pK_MK(UzkSiHBZ|CPAew7}T_N~*Snw50D{k)MdpQi> z8{|_ZAJ(S=pjPguOW`fSUlDsQZKOg0ETufl{X57G&%5#E;1)mZg}nE_r9tFc zbhxaS2iw;&Cufd0i3=1}$Q~I)kI*-8)!POkP45O*c_*qIPK|U_rJ>#$)Z1DAMwY4x zF2%PU?SDb3NmTH1aEXhNc&G4;QYF|63H3`9`%V61+{@Pzq)cr-J3DvGR6lK|g_< zt}bJ}#jF>eWAQ>P@kiT8yAw2VB`X}rU&Cnj}I}Ww-pRg#3eEb56TrI`D8^40M zTNcHSboQe~R!f#5n{crg+)?p7`-$Tb(Nv*G`;F*V86~%;fBuRbI2qDB-40gZZjfFf z`%Db}nvH%pz5%gRd?2|h;{8-9y@aieVQckz$uIs1;ohF}A{6l})Qs&f?esCSO>c;F zJTlzlgytUbS5)NC`L;-(9F>w(L(MdAX@^l`BWq;;Ywk31=Pip;lD2C4%TgCje=3SP zY5Jq_d-#O%F;IT6seA*xLCguM?`_m~niL$^l$&7DMSq4xx3eTC)FyGE(F9QdOL&j9 zqf07aZtm&N;_2N>jOffzJ*z2a#`Z5bo&Z&?S;CMi~bZ4r3TXi_koL1NY?Kk#iZlkN-4jC z6!Dbw0q$*knX*RuHKbXbPu_W~kqwU#`&a@p1Nn}?mLko^7bo5}`K&FVPIctFO!C2x zN1}hbDjb47+Vdy$(e+=ll0Y&SVsDY$7i94{`cdpcVme`ltT3QV*XiBus5e+7Ys{m_ zcfJTkW{v42KM%5)$r`73)%Y2*K_b=4N{&tR!X8+ZzO*_Nn{l@Hrpe+7$s*rQJ^>bS zl=gu6Q$&ue{lX^^N5nFE%ClI4$NIRT_Y-{;Uzk!28|yET2! zJ-@c!YyY+VK6@5c1@a{2Qp1>H7%SC};LbwQALX5eN4|_yl4``I=o2|NX?}C>Fme2I z7HGx`4Dp%F;(eTSsfu`iR(bDW7sQAg!6&Xo!q+1o%ehF($7R(~OE4k1`;6uTQ$&j9 zP7#{!h(cymJYxRv#m0oiI3X77M%9Q4e)V*{@0&$8!aP+DHgO&RZ!CV@gPn5&>h?~A zU4u%OCc?J4zlY)84kcXP(ilhb?FU}20VvE-f?QW)0(XPyK34hp-N z!um5&kyKZ^Zc$1jQ}tWeg!P|N`~7v z7VizO^8Jusc;?cijJ|Oii~%$TxO++a5jBd3;Vxn+-3l+A`bg=V^_q!(o1N`1Sk6fF z2QdL0{w?@%7uh>o@cie#7VJ3-g48>@vjvZ_f?q*Uo?R{2>>Uug4WH4n!$P;=9nx-Q z8|rA=^|ZfV8-}ooQ^AF08|WWGM;X4~lPj9D6aW_#|J=z8UiIQyP}fA2&B4`5Y4ENF z$;&nNX68J`@`~<^3p<=LSD6fP;zcuu_y3aBT(v(2fE%)h@ocK1;;TfPbb-K%ah0SZ zUg4q$=U>Z>1LIiH4kg9b@C9lZF+HOnGz-Ltk62(i3+NlXcX43g`73jP_&1foClv+7 z?^tF=$V$i)N1jshq=e+**f{$vczy|C85B zG2$-d%!8gq@q@({Y20r89+V;Fd_J($O_3`|MwLgSnCDs)Aw*)a+dzzIz(49DIk~R9 zU`HtJq3?8Sng(pU{hytkME$-nb@IXpA)ZsS(S75@ln_e(W>J0i?*^qxa+gK<2g zt3HMJOJQ3vKV*^1!84R9EZa`C>xGJ^c#fTM@NH}iQdkPDSx3Fra=62}cqyC6zYrNI zfxY4xQ+_mhqib+sNH&Y#v;I@G9Y$J#0B7CnMwU7~p^dEFltuX&22y;6$l z3*E%$wE8Y4pCCa^O)oNeiaqk+Cv4k#jmo=wXpv>Yy&%r@u}#F&6PnTd40javjG4k= z+vi=qlhb6q<~m|iD>lLgVzew@$!{XOhrYp#QU*?(cLOh0qqz6zcFr??rdhs+U z)cR<)#M5n%QKp7v@;@fCp9W7r1IE$&*uM)UJ-7|O4J=ohF8av&H#EVkHi!lSp7s3N zfv5m8PeY#nD4?=Zxzs*sloNlzv*`mdj{Rbnt+eP>+D)t{VQ>(EaZ@}2XB&-bL^SB= zLGNKy+;qyXY1oK+Dr2M_N3TTz*iHISH@c2+=D@Cv&vGEwt*y`W`9;Q zaR&+MCVEioVo5NZ{Sflrqq37ELern78qkK{u@K8-1J^UJxh2KBlK2}@3}X{_7`p~1 z_4xaC=&^%(^kF3!^_8-+)PQ5fy~0DU0zEgFE;qFEI1IMX7#omqcG&~3?W%1KYFqMK zo|7$#FA|@8NNMI<-t#FE=tL1Mg>%Ag4=LnI4(v$j-Dx32mNIrRO<#AKg%#bY70p?Ov zJq=dJWIYqwkLLALuyjJFDLkX+K5lfyVDav@P`m@t6&iSW(YBE`dYWvQ%{DRsO=TsM zSP3vIfnhT?$V%BeZ-uOWCVS>RX==QG>|}m4B=ZO|GhR`0ekXHQNM`vaUm-wrjqA{WRZ=Iq0l*$ zmxm;{X6u^#5|7WLW^X}(5g`lk)c)9`7Pq>UygkXwvv=I@l*PECaGJRoF*~FvISUoC z!;E+~Sd0N*)7#>mt)WzbFVlG z>j^C+{YpwI$KXNLXWfQ%G~qDoeey>XRLz2ZhkCZJ}u@{iOMW;kT_ zk-uWR{vQE_c;UVUoZ=VINn8R^VgThCJ*SB(DV*VUv6+hzPBRxL=ug3ozKYcrvRZo1 zX^~1UpB)hEwWhy_)i9U%zmPVQuG?UAPSztv=fsIsn^53>vxjCI+Y#C!P8456K6o*t7Se@YLidPnq{}(_fsmX(V{^-8NAiUR7HgAaiHdmH z2sAiOp7V%zWw!@(lswHT(=oMvv=Nmp?{tm65JKy9mzA=iXG501jn&(H)r-%-#A2U> zWR|gG7yAR#_n*bJU=o8ua&pH1whC(9M73;^ht^tv9OW09m^hIevb?NG`Uh(ISk%Dg z!TrNZ7O|4;8&Jvfl82R?50TzlEYlcx25E3p)Oa)cP-Rb z$zC<(#u{=PS>{5N;casq&^L>o6yRFF2Ly$u*)k_iyf3j^IP{Bs#9s|8S2IRI=EP$LwyYD&oG2sKy}q3JxyZ|%ftb$Re&9e_fyaCN zj;r{pjtFb(DEVZVmIc#@kwPfT)~3>0eaJm zW`7vf{~BE0JVPn=jDbxj_FjoK`yd2*wGE9+I2GeEVCQw`jB`slc zzah?O*HHra-mCP$MhW|_v672vJ1>?y`

UvC4lxQkwBX1%@EJ=46N&Xg$ zXLKc~B1Lxb=s)K}wreBM1IAGY&xo0zdUw2wa@A7+-uO_YGD%zd9lskeLvs(5-&5A~ zd*jPA55DqC+g5I)ntR2AZyC=|Q7K>)zNH3Bi#(Hjx%phvNp_H&ZlXK38l|7@B%ga9 z>2%Q7ijky`3&T_K$sv))@nVyYQ^<9w#Rp=G&K#cROjMEpKowBi!Y=U zzS#VgEVNfd-^#*VKN{7?N+Hc622U|(M}$&QqgGTHBNoN_QRSyhUWfTS0BebkSSbiR zL#SJkNgHfFYrwP4208C*uw?*yfxEb`k7jJ1uegHBfamQUvKc~4?!<2g`o3H0sHHXX5B+U zc@VU&1D~eX4dvb-Yop1s&U_}4)s6DvCh+40v}{LTvc74uc+6)vuo~nK__G1enCJrI zDcwta`!;wyBaW2Q3Gn@6Nu!?$iNW`GZ!!FAq*;133xIQ_QSddhU~*@{zncYTngu&H z8*N-*7A(Zm+aJvo2j8NoP2Oi)jpr))+~D1B!athN*fR#5Kb7Hq!(=kP3BAc%ViMiv z^JVkN?`Bi@LGyVIPw$m&L~ig7k%M5r2|1iN1mij!S{~;xIdx4MSR_6eWu)l$cJyNXpqCD?R@OH6;%=zNI zU2nDPTa&ixpQY+GJzL5WD^EFx!FC->l6IVKw} z*3%uyn-^c9Z;>ig?kGEH6x*7buqC-g{X;D%dDZHw(@lr1{i(NXsJ6fvdJ8(Ia_i!o zw4AL;HHL94bi2Byzv3A_kx!dv@`Vw#Z4p)dduXm2o0eQ#+NkNR0rJ`)EEU2Wp5A@a zDr**xo{+OAsbhj|r?k74^GSv$s+Ys|xlxt6=0eJ<%IwgRx0D{!T$^pFwt7eM$EBY- z^o9-9nrEENk-Wuu9F&NR?@o_({{k*^M=6w!!Bz^Qq59yfTGNgu5FH4 z7X!;L?ckj6$&6OV*6D4vN1_dcI=0P|8RhDTG0{dAxCmEAl!-I4ZAm*^9X9ppI$Hy3 zg#WRxI@mvJKgQ3$#j5YW@%Zp)OgfdP%CJ9oaKVC04BvoBa}&aq2KTem5Myt7b}CZU z(*m_3pS>4(2W;RO6G0U;`u9_%aW^P$F8KC;;BGl<#mWq6yTCmK4 zXXG;Kt8MC)S54M<>+J)zdFKU(boQQw}Xu6cAM z#o3d$m%fJ)N1G&^p5Pf+CfjW3o`0FO>$MC8`Uy`NE&=K9mf+tPQxGiPO=JZe&dHxR zzp(H0rd4iLD}SIm-+0r}+6+$_jx%btdMt9$_8pq*aH}KdV|#tM1|^Bp63%4Q%iPjh zO3kP*Z-wDVs`W;m(EhLTlv?q7NYvi5nV+VM0~dkJEJ<`UKQ*~k^DK;JBcQhJb3?(VrKU%stM4iB>*{+p=r#yC>%9lfu~^xij%x@D z;^56`4)-sx|2d{NI1;wnwiI810iI~v=1BNhZ%N)=+`DNMZ-I5bR2lug4p7`y>>JU`E`bxntT^Bcwoh7j6u8UrK4UA_e-&v37<;louHqd3pY z&}%aij@gs9E*i@zBWVZ1eMz*=-@qJ<_^)qs@GL>od&i6SSK+i1`b5iNpt*a;>kS#U zDhiX_Xm3W&b~I!l%ixlxjHCVI#ltA5x%QGJVxI}UVBGo zjKdujpR^+xm8ehE`4ra6S%;}po^}4Q-^m-JxGuW%yO?OX{fd&yV%|%N210Mx-ii?P z*l*x#TFy6^7gIfVcfh|sPt(7)Cv4Ixzf>z805L;9tLf)5^nL#EEhA@VhAuMn&rxJ2 z?tC;&G~(FGD7CT-#n4TC)iu5BRnaRW)XK%cxLTuC*PBq6ETc^pcz>^|USug@UA7v{ zR+o`bt5$3U<2nV3I`q#R`sR$}+6;AEEo2l&>$TF0UrgG%GRoQ0-B(xN!qP}t8R6`k zs;(X0H!oEmu4=B@h?O>Hw7O<^-;}E1Dr?_~h+6{Kk*baO0CT9c6N1!m)7@K_lYj^je4tD5p z7Al;TKPrkYa`&>cRYcp-sI%Tm)Cas?tFCqSYDXw^Mz_mf3^n=-A`AkAw0j=_v)KL$ zZdcgv>hvJjcO#RJs};{dL(dr83Eg1VKg`h2xmqLDWdmO8=+L*|dl@lU{nc=ks;lZe zqZHwI70txWVE9-C{~g21FR?p|#?O7lEFNyxPuca)JgLV|rKziaz~5O{>&5Cafw>I} z_Xzw@iIx-fkY;O;i{c8@YaIAjK{1Rc1R~+H5$+!w?SPLq?Z>7FKXR6qv;#|dT#dGS z9PyaF5a6p0;)h%P#)rDL|HFBD|8zMI)-G(ZW41_5-r#(8tY>)ASkI`RXXwW=^ft|Q zO0#u1Y~O3P=B77b$25I^n!Ya;@d-ich~2fn)n0o9w`}F_s5PhRdwlg~)b5L_$6%-O z7|EOX%v0wdaKJjiqn|mpPU%~y-e#!hI@5jr5>3ZOp<#k2>gPf0_rmU&wPK)e3Qhp_ z;17$M!iGFG8_(>o^3;4Rk_)|g>g*4Y-=+4`KqJreCgAGu+@|)MRResZ+ApOgBe|vY z|LmtW_Hd5Tw>WIaaTzN$VGj-w(j(+lrLOH^muo@IRUd)LDjK6|OgolREqVHwv&Yq< z&1lWyF|Ati>C&BcEDlnWKTs>~g#Ylpo(%^JjEBg_A|HdkkHOJ|twXczaM+qP+YxUh z#s%z|s_*NvXR~YHS(r1zH0SqvgU?d5Dy9R^K-x~)RCX?m9O-OzhJEQk3AxAx)cTa)l5fEsBGKmWGt+vr%Yoky=T&+z_bqb&?j-Hz*fT=Jp! zP4&lgsz0X9?rQ5O{oGY=Oa4)BPVw&wthF+85ai*bTx4;Hzcw+!{Q}xDF z{jj04tFg7VIWjHjODNCT5W`ifuhkxoa(JRDIWROEoO%9M&XyQvIOnRi8~yt^)<+yD z_axQ)bgNFNg;CI&10)|S|LpB*ztk}?YQO$t(4#S0a((I7();g(U@dYos)@u9P8|Y; zIO8VOWFQ~}F53L;(|cT=-ut1q(7KL1-SH55xdtn`n$m-q$D=#@H5G>m2q{Cydivjz zp&!rCcgnuRC_qR#;vIz2vKCkf(QAS(V3_R!-g4PDn<5pM20*p(O%$mH6lvT zG#-4f0W%UU6c8`i`STHE?5?vm=R*F@qimOhYpYhx#*ut`6p}H~vIR5>p!3n!y)mvc z;o61U&DeqnyK%b>j=S{`UH^O1e?X$OB1t)`C2uZ$O}6>eMw>H{Z#iNeSdhhOo>BFf zT$HyB6ZJqoB_dBxk6fT&JOU4k$;! zb;RG}yY?HVzh~n2RL%GmfyKbx5rz}jVZPrSk(U1@_Z@R;CZyW-ka{+4&X<=VU)vg; zr;lrwF1^`wX&z=-T^g<|T^g?4^n}q{_~s0Hr&hq3jS&dt&{y5rAJOY3C=)RB3R>1Z zqddcmK%qRf=#+v1fag(#ooRFsJ{w|sWcUK?&7ONu%-xsXAOt}n!jr|9Q z!76hnWF1hd;qS_cRJCxboFUNKIAF9!q8O(z4v=fW*CDbuFhQxpGkIxh{!T(?NIcG@ zmN=OlCt(M1&B*2NCfG`;*X7An`imAOxl8xw1O8?c;&>yf7faskfAz{&nV?KiGP?RQ6`BYBTn$@x)+og6t#v`EaAnx}uA zzJ-AYvu|CrxqnmF8ve_v7xPC{Sl^`|x5I(ewKewI{V}OM_vq;ule z4+vLIO0%_bQ^Tm7iamoISx;f!fn#DVk1GK(_P~i{urUJ<9n%+cJpEJP-;r3(-~|^) zIF>!J#@xzyvj@v?#v8Tb3C3_=%t63m*g?$c8m|8`tFF}$hze5Bc!X;il?pUOodRD} zg5e7?TNscIAq?HlSTvDQX^ad=HJt(JYQ`c&cUT&08mxa6b!~bCo++4~)1#Q_ib2nu zw8o$eS_m=7!C2(IBy{aDmoMn z4?Ml)nizN^_~}~`8n`o#P1C;x%XL^BTD-)FhqrPc$$0oa9L(XK!2sIkz>33mHlY28 zei8VaPTz@x52YAmvXD!-Ju5IUG4S)h!*e4D$M9sR+63Im6Kc_yE5NmjTpt6o8JHaa z3cm%wao&hL#`bg+o&6OS(=~wMj|JUl*FW}CwLP4_#>{wK zmp5_`5sgy?&orzvj7^TEz3ST3XBh!TJqrWHIC{4FKQi>F2DA)E(ECbvrSd%` z6}ygy_uzGiJ~*~c-k?^z0yfNuEb)R-qBz|%@BvJ`{!Y)`qbFS!xt~SqBe>ivG>R0Z zdwMUF>phEedW&&pX!qD<8U8*XCin=sIY{=e$bW`+oHk1#Ud32TMf#UA*Opippib{0 z3=&hlFOPMq*MKbIU1;_8W2wp_sApwYkw5%Y5&nA!z7{#b*x90)Myb%IJA(C$-IF3~ zx>c5{fgND`B%{=C(mnm}4~oZ8u%6kXp5QYL-rm9%4KKnf5(ej+5x-TWiXrKXLuQR1 zq68)YqsI4*;=jSZ7BwF8UJc2e{6_pHHKR`ZbUp$WpptPT+gTl`eu5r z-a-!S()hZvFOxgD+lwpD%XuYlf;#z7L_AvGVs zq;W(pzOcXR-yKO5Ud2rAj+uPgF8nbV9QX9cS=Mh(MdYdHxN`a?Pd)Pm{&pLqz7X5% zl2#CZa{|f6T18#s=z;R*z6Snt7TRFjhO`dEZ9R|y|2ZJI6Ujz|;G*X@JED*@Ai*+4 zow>!X_G1$!ckkipJrU0ye2*1v+ip*;RUe;-_<@xthG$Q_>*}tqiRv*S=gSGU4ban8 zM|-ED)EJMWXLRkED0|g;_T;+7i?rot2U}vwf0EU=v$9lmP46D4C)KtMm1KWhdNszf!%W1(2NcFLQK%Osj zLeXJ3OuTL=lK#Usr$=_UYRhX6_Rp_X3NKV#b>;b675p@Es^_mb{*PAj>y*W99iB&V zTmWE)a`!+bFR8}1B~|~{^A!F~g=nSrO#j-}{`yC?ZT(MwT6^w_rDyQwO;OW7(l<$X zG{GuWP2Z${YB01(Ou9`?{}k(QTh(0=4%;Voyz+cJ3oYV_mF4}c-PPJdeT+?AUycu6 zvhr{U?)mBZ!V3#8#LS8HXu+oSi`y2rd5SQ&igqZVo`c`)ie7*Bz`F-xky}(l+{yJ% z{_x}v=5$IoFE|SJ4v?_fkj{>&^n@M09s!w!J5aFsOALmsTqbjcJgtEPArk9a>6p!R z#O;wKBEfU;1HcF2-XN&cFaZ`~p?eT+eDGsX;oB(pEf!KnmX2?N8&GcMR)CH0^qCu3 z-FC1QatS^S!(kTJ`ItGe@aC$nHdXC6c^UTTQOoH5c#W%8roli6qL}>-b;G_1Yu20N zK$BM){mwg})x>E?#>C;J(K^00f!M+^MNDr`1Oa;Iv#P?`nSkz&L4_QZjd|+HLAU)K z*J(gH0|eFr#bqDCAC?@2C%K>Cz!%U5$l^l;V2ph3cc2c+o>UW@@#_F)e~W}1 zprBykS;%+BQ``yYFnZEnmBID@^KoN|06Qz_OPQqB9Xhl{Lh)= zE(u!|ofTT^|HIn%%D4vk!@X0{PkG|dOsi`v(M1GPi-aW5~79MbR4IP7$nUjvLzIL%o=OE7OC$$c4jk zwYZT8xiy|c6V8#l)>JqF7*3;bq1nI96vRzvA;xC=PC{4`qItly2hS}&{~DuMzoFndH#ps%ZGKd5t<6Yo81f+ z9usgJXSb6|ld}-a&eH*|;2{xe$zu;W*f715g{Ht1j$URLVwn%MFosW#zWOh8g0e6C4kDnSIeVz7-|?zo8i?oC#XDJIC<8((-}8xTuOA=NH`KtnRKmUJ94LQ?a`v)V8xowDsM+73x^sxAg+Cxue1M^Y9dd3rO3jdg0h{6c z%`C|C7sF#YuQFBmQEjc)eRbi}y2*oV;uTAuxUnNDicvBkngC z2RvjjqH-_}N;R+}97ch5n6nt+9>akFi*6>z30Mc5`Li@eA3Irr-3NxrR9bo^{Z$4D zP|$4!7(^IN#uV26Jpi8bw0n`j9P}$VC=aFD;8ex?`q5SU0W{8c9Owjf4*ka%hu`3& zbOFV)^V?Bn@wX_^&H;wXV9LV;vBGOV1ZWN-Oq&e+?b4J`qb(H@Q8f?A+B+zTK8w+d zpiCurR@8QY95u{wEFXZ#Hg+Wp(vKxLxZ250*r;+&BLt+v2Br#ZhzvS_4>4=#i9;NG5OXVJ)*mZl+E%mS_ObL2UM&}*~M>}mV)1dVT^cK;0hbl?sk!88j3 zcgNq1BnH?W!0zs!2=f;q6U)d=%s?Loqob&Xn5aZtGO;XB)ivWHD{4wJU2T!h8<$*( zmAx~@)rR-Pkyy~zr{kol{I+_Wj>;QzSU^azg#p zmJ@0}EXc=UYLYm>5|yqw@dxZI|1d?o)XkG^JM{BtE~mnO-c9lDR)HT zo?^eAsJ--qdb@11?>+(AlVUv|(cH^wAWpkw*%mxX@M<6v*FT;m4vTo4Vwvk~cX}dD ztMPXJsB!(_jOJM|(tX!(*>cUj1UDk^`XaR|HU5f@UmA@!%#hQdxtCI$He_i79=4^J zn3c*Mn#Z18npAI31jMRPP}4VWwvU8)B|oax^w+4BwcX&fkJN0to8U6?K9i}xYYFaN z;1>xWmj(1~T0&j&@kRZ36Uf%0>9Ub%yOOY5bKeDJAFrWc+%&^J5U-0n@2fj*@d%)^R?v;?7YW-qi_4|9!K8}+)Y&1^y|8rsIIY%sfy}> z!%*3E>Y9eC%>eTBizIAchm%7E*NJf}?JcwqiaY1H&P1w}E$~LXi7T6;H()2m3tzU~ z){$ZvG?wh<)Mb$!>Y6+8TI&1As>#dHkUOLBgtOflS=ApaY+=LgoIR+#9Ca$J;e?I> zZyrT$PEEI=eE;rMd`B*MzuRDukNZEZr$67>s2|(ejQdVnE3PI&jJhlv>b@0?EcWd; z}iTm8p7WIR@dQd7{=lQhqD^Z7exwFkRtwsZ2T zoBN$T;~*2)AY(+iho=qAc$e*JAkzI#qe;6|%m7WRcM-u4vMs)>`yFAj#fZ@c8xfMt zwPBi;+*G<8E{b=5xGpX)y;<6e`>;3|_uYm~z-`#;c|6*A8}=gHhP}Y~ONlcs!pdu~ zBe8Rr{fm3BKR`g`*0Q6W@^J{fmc96391wj*!+@EejdGcyEFPATgI_dl(QL>3m9prkuHlO)n=AFH z6kN#NQre8ITYQSVkw~wvmN%t#?n};$QXh}O(395-Yoc6dVoDEa`hH2~C5|uG^KE}# zwR=I7K9;BTxDp3l^3wF@GiM-UYe84^x62!l`gZ%yL-yp&>Z5s(sC(`DX1nbKuOMo! z4S0=R9|d}v{zi2i3i9+6cM)SsU$obkPXhw76gF-uUoG>h;BcFM48m`7M>Uq5_;R}h7^?w2PwpYCoQvs?}j|U8{_MVHU;Jkc{KI(ZROw^UWVq8pfKNZj3 zz}27rvU}9ZO#pHAhn*&`wcH1X;pn+3-E!)cRMWJPAz%N|)BiU8q|3vYFa*UjpuBez~&^?XvVIns!~LX8U4kTL)~rxW#)pyx-7s@i^S8 zp9D}mt9PC&>aF^ATpRxx40v7VKuNA&a-rU)uG)?0c5#iXQSF9vz9l%W>dE0|yr)>z zZu>;b>F}QSh~NFgRD^iUBsc(7wznE>A0*q|px5CI0$M%D(&#$9fsJl88$C$2S#n0B zsd!b^vo84=4L!DG!8+WKfPdj2mK~M+)WZXTUfhCli}@kOnPXTbVJi)?1qOTc4*CI~ z?Ech*ZMJPo+xS(JB_m-m++e+so`5Up33_cpdJOJx-bs(J@5KAxblwuKKz)kxR(B-p zdLe0gk@n#Mncw)HlA)i(1-_)C@)sZR?JIw}=6V1V<@xwd_27<4yp~zuo|K5o<5M-a zs|HfFTU=Z4DADZdds7t8?G5E;dM>!d<9J2!%)K4&{}Om9civdOv9=be+JI|Kq97|WX&yW)^KO!7O9Hrx|(m;)=W`g z2psK$wY)7(tr(1q>qxKiwn+8ii;*|F>yobSy4pQH5${YZhJ(m)4b#b{B})scu7Q;F zpmMeI4sSnXVK4NqS}_HU^0%_;vIA#m&tDCXjN=&UPH5I|naGT95 z{hPWbO&Ns(`~}KUkZ>GN|0;^tq9fCiPQlCGHTw;Bk1XBh-(_kgR|00YIrK)O=VbF7 z`d`>{@=lPs?&CF~PG|JfQmtGdRS7itLmr@g7XUGA${`V5fL6pg)log2=eb0$QMjAP z??VSPBSP$60hf29k+Ws9U|jUVY7!rU-u`p42A znB`8vhyz1hawxmxP{vMSY_jp~W7DjfUh6liWg%}rnzh>Jacadc|G%o+h_?S^b#!;c z5sWW34@2xNx{c?*YhLNdpRDUXSywq!!Z_R*>+@k;Vy%|mquI7M;gFd7As;M%oB;ZD z!lPjhz@4{qmwwlXJ8|yx7%cb(nhUNCTd)(y!m>kd$Pt;ZMZ7|NkzgK+7gp!-+avmR*S;QDLsmT%MRt9BY+CZR z#TU89#<*G|mf)WMUQxddel|9ysgC}S-?jnU+xVxM(UQZ0_aQKu+{o{FJ|){{SwgLa zvRFdV5j}H-z!k28SDVJ%7O*SF5^YaYoK=J74Ei^ylU5G+c##tPwg%z11aGYVH!esu-p|bS+A|C-tw~ zQ92A)R_e$02duyVu!Pmt<4Yvp9EongRRCjs0RC9w!TTp5Sw7m7mrTO}Zml=u{|d0ao8PNRI_u@Hh3LES zWu2sU@1w|z>5FMEzx(!sEo1p!TwcAl33VEt-!x0 z3LinHFNTzSpZ)|G*_!`H+`E8BRhh>s{}C-Hj)!O#MGVl$e|Z>YTHm^gsUo+qWD@ zWL`7s<{zYImPx0dq;&pr7*}q$H*J-*W;_|Ye6;dYPJW+TmLNXfvH)GPX`2*aHXFAE z;^)Rj-7&&|gs}XYFQBC;qLr-8$6#ZmwV5oacU+sSH{HMR`(rlsNHm*~JdN&ibB5fs zRPxt!7D}EKsv+nloNjOm%yh{DHnvnFZ>8F^{c4T7t^f#XO&h5X0dM|5W|rH#{dK)y zoLT|m%by%TEewTmsF&+M6wn?x!Pu9utsA6DCcl{N}Qrlwj}Yn}3AFSMZd-5p424r}~EAWVGxCP~dH4 zm*8cO0-%!b*KbRF7#PxH2KITARcXtIz+4t<*xqM6cRm?>V)ov;0OuLpMyu0#q4!g! zTdID7sw=z5(NEP%9!G<o9L0h`+;HLb(Yk#aYfGn80bM zKDqfAQGm30ET6scy_Vh1e!Tw;$69j$$gt0TQ(TvDQYdWryxQTGMA9eW-Ao8GaZr~a zJRU2NTN=OMB;s4pe1=XI^=}ytF`-l1wiXB4<@VIJEyaN@du`JmQ4j*sFp!(9C|yHhY{zF(N9;%Dsj#mks$29`vVR2E2zldk)Ezl zEuFXfF5bVW-=`$H->d4wiwR{h`;Lm#s)iU6wJ~=*JsX|Zvm&W5eQuNHn_`grDR}OByfGjJ}XobFj5EUfb{qW_<~tAU9%@z_Nw(C z5(p1X0M170DyCR+WHU$JVy?6M8`%nTUfsqu9cwx-7i2g1+s=>3GeaX%_4VaaHw_+A zojxg&<~WF&)ib|E>>6`63O6a{zvh*hm+}C&%TchEV*|{-JiCyYvI4EwwZ3!5uD@KA zEFD{X>lYGRE8;&)Y%^*bdy0+Z9DL&G##`07-FRQkM$!M|JzT>>?ak9AuO+I9^D9Zn zCJW;+c?3iSh=8r>7H&1zR321XxFN5y1m)dP05?Y;rJ@_zf*zsdrAP~XZwUqO|k*t#`%ad|$eo1L78eYwaHTjx}ToE_oTwcRCQbMPEem`n& z(4vgisnA@^{#_UEYuX#`7P^NosuG6ia)2(=HnJe?VQYEB-c_n)A#f}(ufQzQnTjf)p`hP|J@ks|ow-rBxo#TeW zk;bxdBfaBu=nowdSr0|dCi$_|khI3r_WQV^Snm_;5w&cD*?PL0;d6cEsM7uTnkm1o z?&qXD^0IS#5UGdQK~!TKL}vbE@?RQh3`GtR_#c#;sSo27XnF%{565agj_(!`E0$4N zsiR70Cz>D}t)SsFIof5Y**hsDy0Z&S%za9~vXc6uL}#|j@2F%*plc*F#hMt1+Kbg2 z$x3i+_hRW+|H3zKH1V7qfu_H;VKdIEp^;K|b#p9aMC@wfdi`cmc~yrHm9yUuORAKU zW*`~T;&RlnLN)O#%?>Lx67`0vpu;5mZ3KNHbntQ2xhV0xt=u;s>jjvOEd5{+{jy2| zi25{mUN=-EyI)9+$^iF-rmqqVomTcjOoYMqDWrNHBa!0E$1jw^uXaoiyihWVp6myM?d8Wl!eQUmH5!s<8pv;TZV5ELT zL%w}cn-!nZ-BVpkn^>zUUGsI1q4P!_=Sw3eqcqjuL4w(izLXI?@cR>3&x5_+XLStD?vI3+g9pX- z3$FNA6SV=%N&}m&&hZbV_`f$FYA)u{C_eZ^Z$RDk3KAwX;Jgx zi-;oxTOz_+8;&jo>*X8gh1=3k%y_vA{x@(A?vv! zv1PYuztR~D0R-W-AcIN}Hi<^B?5yfVdleM!dw9&o1L~IdB>MVNC-^fd&d7E2NHgl8 z#KGflnMK@nM53%mea6v54E zuh<#2iCW!4)N1!1peF>st0^Mlep_Pe?zUY)jR!>bt7xC?XbW#yHNgpvk$|6aiSCh! z1ea9%Iy||9{>Y4suUaRIBnMyMmx8ANZ#)vjqiCwc?@pBX-HCvU_}z^XzYB1y*j+0m z|3`T&(1Qp@TnJf%`runjSOH-W&5<4vbUZ{cAN7pnXBeJFzo14x4Nljas+JD{aoZ%g znmFZk0?2%P4ikJW5Z!=|GK)kx{bznnq*dlv0)x>%t!<9@cl8`flB4#QgYiTv>hWFp@(lJw# zm|)`WSj~?3UFN=aqv1Mer$Ez60;X)12Jl{|-plY4Q}Ej#{%Uo%~vzQ{`%JK3+8r=mqpNyjYndj zKeZpkKOgRmXX2kfM%H8DAK8(6S;GC`z|yc^mgeU%dOa&x%D)KychoBSM$@f0h1xUl zU|;sVUaNUVk1T>W{6V*5+mc1VpjiYA+U@?IBM0rvM;Um(j|%GO>n{)f#5(pHH?fXK zgMTpFm45JFxcol;VF7th_X(%VAG9t%gMSJm@K`;typDgeD4`h2gVm4FkXQ^N9#-J% zC;=H$KjHts_gimzhjK;xD(llhJ`LMAG~YtJFW#Gt zt3vK3;y?JqDO*@u5;OfnHm1!ivYR%rIw2n7C(0W9t~jEK!}MnSJmnqn8!TpGHN;sP zqO-Xj7gF(+mY0@Pfm^9mD9fBT;?1rfejsmD9EtcA>t`ys03T)UXipriHK`DV)xIq6jl7WiOEZWG;l=Ol;fD z(T75GB2L*|b{F|P*t+iu_Nmu%R{qg1_4-G0VsqE)t~a~>53mSs2mg9m{|B0<_n?F6 zZ64nnC1|_LUlDhzknJN^pZBULcJv;f*_(tAEt2&&tFEtutiPdZ|7n%{9Rw5E-#bjR zGkUD%@Ab;sd)sg6Bpn>iIxl1GwG(pXZ}PwQ!w~mkc=N*$w{yuxbqQt1<&|wBtkjg) z#+6dIV}N2Gp5hwz;WA2(&1E0F8!&tMmEYS}`Q2Rl{LOIL)NvU`L>6-IlwCp0Az8A7 zJKW)YnI13lzn|Rq{Uuz|6rHQ4bA6Q4`?dZOJb_t*`jkKdtx{2J9Cf z4B(|1KQ`hVH~9KO_^h%M5{HV6`9B3TJX?HSVS&9aQeXC?NMLpE)+}ovZB!tr;^0}X z`LmMJ0kM+;%_XNoA4%NvTKoe=(mv*-NAk6B<>4CKPIp`s@A3U&8g040&XuabWM-LjxuqOV=c49$owE+`oC3%6E#7#F}QIVw5SD`5)=7|E8ES zfQ*>5MrP0f`ffPod=;?|#_Zj($~{@g=QNsgN%Mg+^@0qY489%5rP}p!@c|{ZUZiy*bzABV}Iy`r*gM_y^q=_I=OUrulGr z<{QAJA4qcQGbP@)MU2Sl1)(MUD2pb(Ajd~he{x>x48i4UA{KDjNqz~O<7xo=2{7df zThct1uSE)=kff;&llb#L^$g`Sl5_dUpg7?UOlfDpUL9D~dT7v)y=L>IB5VZ`weUjx zZ7{90hPB#o4!xEPZN)hC5p~Eu~PEq3{=c!TdOdIiZk2;+iQpY6emVE z5mng*syL%u7XQA?Jf@iytN38nN`dXLel$r zJVR+Ed9r;>&Fyp_e(h^7?3=)@ws!}#x&tA(`0Aa^6bivwAK&baq&AHO8Z)RB%rKG_ zs^vzY{wd4<=#Az)-Paz-Yd_fP29IoiIozmjpYfG3xBfm^;XMhLAaqimi9^mxN{T&J8IxhS3*GPsFwV--8PIAupcZ)-G5wDdndylS`H6%_$4i z?7jI-PtMNuD+C#0qSP;kNEW+qUj@>N#s3R%l8TO!4@-KvW`Q5Dr(xUF%9CykzqMjF@eO?RS}5O_%E_ZoYVeMzBdxTp?H2BrNra#uwjjOE>B`o zn(q-T?@UPVA+rVEG&NKBxcL@tNjU;d^zG8h9?iZlmT>&xKGhtpjM0Lii>Whfgx0oc zF+q3nIAeg-2V zue|#NCl(=AvM}M{pGn>&Og9k>Z&%<)CHBLL z#Pfm}hOtoi<@Ef~-0;1XzDpjb?^d{h^QF3Vst4PDmM5uIx1Er_yOt~E^RS)(emQY( zMTNS(S~)7vU!8`YJfxj~R_GJ?syl0Esq4yv39nJR(k7nvyHF?`$o&kK^tGh08?BU`^6`4q18mZA26s@;ZtAElNTH1_sB4fgf! z??qm2eu6qBR!{sIr!QJh;JrP<`@5P>q4SUs5~Z`7M{Ew9IEM`?lPH}-CwP~>TY(#g zOJTD#$icT3v8WRZ?0-s*^}Xyz*-x|O7K(?XH-$f%Uf2iQywXt%#mCmT%b;sIE-V{Q zjBF^J^QZ;$3Mevjci1NDv7BV^G2Qo}8m@W6SnvYyu|J1?)f@Ixj{fp*V zLAx-yFviDvTINeWZBE3nQU5)l=Ei<~|Lf-Z5AjC*iNu+Wmn1gUkUdrn6Hs&|Jc0ds zxbltl+x>~svzD1IFUC;Vm>7C4=w$q;cND}_hI)a7QX={^ae41tu;73k%+ktf6hJU_ z6E%rH`ZGq(8W02}FxGdYsRh*pkHCqVgGNgWLrLEyxEHH`J?5PJ;J1kTePvww^#8I| zF}tJ9J#&4BX}{q_L<2O2_vZd}W6vsCA~<9Y**oNI!J)t&#wOMH2xjlD>Y9bQ-LQX2 z3m~HX6MmQTe!h~9^s_dJ21d-@ftS5&Vg2wgS15o35c{9-WDy%!Y=bM+ zxF8}APX#9BdMp{_t~uzpZnT_6P2O45iaq)niPiH?1U4Y;&jnWAE8HZ0D{v_F#Mx76 zdvwQs-svH(<;I`&~GoEnidiPZ=I4cnXnP=x?!8PaAhe;iAn8%@uXUPjaRW#J?C zE4{(qC$9uPuK=Om;J&sJ4#|PZDNYCdcSJkDK(c8vY~ic$tDgNhEjZw)_Y;67PMQRfQf%Qzf3nw{enFDQFl$M}&1 z2I(fF_A-G`)K=fqfp}t(*>qU=3!`O@P)uR##FAPZxvq5)4@Av4R5@=wD)R%qCLzV_ z!eaN@pJ*4*DF9mR=L>@nxk&vD2tU`YlrLk>g2H0H!Mxpl^{0y;M+aKY(;WB-PF&m^ zs*$LHFd=RWf5BjKpf_rsjPB-_%=8>$z}_Ry6X@MCQsp8Q5Gr~qa1?EmaR6HEk0i2l}%#f z6Ym-vN2s1JI9`|m&fDSioz)fx${ZX#_xr>Q*`4dRt0QQv%C@HM5`wWFU4Nw4RJ@6~ zehb@`RnvKAnLZl;buG`vI}L;IA>NO_LnDzE!jIls(RL_EK6hGJ6*v?oWyaQ^6isal3^k<7KiOnh)cxbg!Qf2acWoY8Ux+dhmdI}g0);#Ti0V5+~j z(%0f&_#WYUvb{U;k?F~J|J^xM?fbox5i$fDop=q~@E@n(a9h76Ll1|gWVQk+nJlRy zC8subh85~}geA)O2}KZmB9^|BhJffq8p>@%8qG9BBNJ!@lsH-=k|ZTuVXD5AHEjXFzDt@Jkp8I?9iPQUvnEw8=uU$oqCB1_=fk*ewLyEXj$ z@kZ@V8M22nnwIA-aVDKTQWf`!ID}O?DRkUBmyTrQWdd37DuRHIXDBN@arhQIlnq2e z`&)~e_^MLT1w;$pQXM+OPST^FGaQNqxQU3n()S}q1Cd; zpjYcRs>I zoXIsr+#C&2B%CKD$AkN`gVe7PQt#k;i}Summ3o;OsZG)$r|9zob67PUr!y-jPUkWq zvYC@G&1=kWC^k-Z$)Z=;C6&e*_)yW#qMVAlpI~}#A+5nAIbCU9D{a}F#&yFfBkjHH ziFNiL1T$n}h@si8_d<1**ebhaqUTKBVJ^A9WAI+@EqYBXqQ6hGCmzQ#sDsKrtSZT8 zL%aqt5zmu2L;|vI`ASny4=pu(kskgew})OSC3yIayoVpdi!nG+Lb##zz@H0*5qss_ zdR>u@&*!_^yzgF+@0_9mc@O`fA8rZe0LJ@?Zo%zXhoLYrBjU?)9)|T^r?#i5XA9kp zUk+sFTuO-EtCz#?a=JAPGalhZiho;%j654BQZx|mvCvB zj8qj>JG)O2c_lA?y+R@xO#4HfF5wg+MEvX)XR62sBUvOB^-Gnom1kz-C!{dg0cgU_ z)6N0e5BIQD_{Xs2$9Xs^)cZX`Uw_;$PU7f+>k-1%Mf341D$j;XbV#&E%zjL8*AI43 zVf|V9O($FBole7HBxE1Y8LMa#k7=mXzsvUvhu+q7eW1XS**@_2m z?)cT;(^vgD@rN5{1->4CuyNLaiug|=&aA?xMM{;j$h**G_heS7oQqs++5;Oetcd?d z1V0tor&SLlTB*@EtGA-z!^R7T8L6)V)W+KbQ;d|Hh1huefC|oP6F@t+7gp`lwX2Gs zwScib$m|RkfSCneC57DkO~Haj?(ye{+8fO)>PW!^T_D9pUH!Y6tShL_%CoAoU$c7C zk14&?ZuC~{IAw!;rRLS#EI3s-)$}tWW!D^>d$zs1@)cq>)xDjN&)^)MJyr3ziC$E3 zE{#L6zS}dP&)A}r=eg+?@R!=#wxiS>b-z~l98Vis;ghi^M07qNbJXJbO5wB1?&9Q2 zbgX^7>70GP-A4GQQ?&F_rRUiZsqZq+Dr+_i7q^I_M-Ri8|9t@lJxitCXM7lo$I&x<5b|uA7@KBm8#rC49?y~P`xmzsmV+R zii~NZO-U~(6fGToVc*F-vh^cjawZy2jxe6=;b7N>>m?HzY9PE;x=;gg_IK(ySshN% z^1}lPyeC*ri`?J2?8OS~=mjV6NFw^~_2;eKk7)X!s;JtB zlR(sdv(El-x*dlpzUZ&yI@DWa#D1as#zkiO@sL6a=IK;>jq!YN94C6dYY0#ETc+4< zuY845PY^RW+!N`M?qX4!NQm%O!z4CJzO@27Ea!WiBDzkn>eT7R(%_W@-cN=}Jv_!} z84RqnY%!IlvZp9ul=?&F9lb!eL%QAiGHB=S~!a-@D{NdLKmchlcj=0P6g^i z-F)yArS@yMQgB+S`%D~<6jq~(oj@h)w^gpr^xP!SOx{sAIZ$w0ja{EpdeLr%Z=@!G zEoNe6srO}ik;UixG(JDXONGz$EN7F>x$MJltCnh;l+eH7bdYikMnpj0|Y z)BbpU2VO$YT~b&;)VH~R{aEy}=8%tu^pH?aeTIvXAW|Q)crPi@Z}e{8zC$=R_ba=0Vjy?~n{YDODE$ z;Kc6)-%FJ2^aW z>QYgW&q#4Nk^w3TuS>qg@ZmE>#n$w)W zcRH{2`pAsN2crLBZFctDJD9BO%zgHcihGjHY&=zDpI?z%=-|@c-ist@0@`bZSw(C2 zG45pF1@C2V-v1WBShm}lCmKg_ zuba1k4%ON3i~rVD+k~BOcv7xk)&(rg~U+K*otaKgYUll(Vs{UuSU}|CMuI*FF zZbMHBZ+tYBR|L%{G(7bX9rLwbRr|EkOLaM-%|)Ih%FuXR^xj9gqYkPUm|!Q<2b>_p zqY_ zG}MOtK1`H;M+2|=b&{zt^PN<$&Ro`r;O%7{Wq{<*19+Y_Zi&*MxM3!k|M$JQAdp;D zRvMin*hs3bx(wjO9Y_-M0sOR@?ns&k!|qk;?|YT4qE)n!ajU9VkA1Cu=Lou~aD{IA z%DQe_@b8W1Ww!|;37<|6?#%1KT*drx8hdw}6kV!{vN6TaqG)|y(Mc3FmhJUNTtL^G z?u5ZD2;6cqW%upQ9oSH*mio^8HBFOErZusnu<=mfmj6JT*C549^Qt}w3t)%w6kQyW zywIGDyAO|Myd|eb?3;hV|I2H-=5}*tDjqzXt8Jv7qJDxq?YE=O1EF8=2?jc0y(hBd zy7BVZFpZC4Eu8Ya<|jbgt)E0&4t~L0T6M+9#>5EY)SPw|4knAnKEZ4UYP1m zSD%wJ?@@`fZU!3_dmi0@~|WC;ZOC$C-WZ8;~~ehW2`&P?)u68 z|710FaAM+5_Rk4&xdvzVsaW02nnQE9IHO-;_ytw#pttPg#bcf7x8#9oF9|wx> z5x9Rgv_$4DE%%(0(`>+`>suY{^*w zBp;)GQmJV6OvM&N~TMiPs_Q3KSu}mIlzy5Aqsw0 z{5e%SMZG28=BT&M5&LD+3TdKGUkM8uv-jI8-EX}nt8Dk^*Nx;X*+QLpV(%o>VoII8 zUo=$}-8kYj@-f?ZulvWhmEtXV5V|4v75ek0p4TaW^E_2F^}NA#L$s+!mSlWYG(E=s z%rjy+8bj6%gR8{HX^{816t6g%5GkP}xvVmyf+)(emH!{L#&D$B`x`H?{ z!n#sZWeGtWP^koP6o6pNC^i;+hNl3h*sFxzDz-YSaQ2nW%(cgfFvMcMmq`xo=VU_8 zPF$g8+U%1EE0xpAP3m!upfSSUpPo<*AGEMWcr53B*^EfJ#DOV3KPTpLHZsYd_EV@> z%@^Z4Q57azWQVL@)7BFtQuS-1OhAot>L2+U)H7O6r#2EvylW)3mq2GveViAl5oms# zo&3!d0R;pyT=N@2R-@%Y0Rn>k(x0l8B1S2Del@S$pXELgM@cu$6Epq1AdK3Y?blG! zDCOJzwLBwdn3=v`zNHG~sfX?MS2UbBG-mEFK9oulysNZw9~Xy<%^ zg8W0#A(^Tyq-x=i)(}FL5h+EGtViLa1iMJCz$&7TSYuOE;0r(m4Z>gNrscR#h-o$(2MRUWm%%%>WayiTx>oCoJ(F`YTm-tXO^(Q=CSW7&S48;0@X z`jOqs8R>S4LNdPN<$~-wit(@}@8KlA*Khu}-O#A*Dctm+C z^Ct1Upe8b0z0%e`DZ9L%2ByyLVTKm|xri^)&k0M=iPc5wW7iO>^+(VxoK}=n2$%hd zSmYNDP{o;_Ic^HkY zbR6+KanBg)jzJVQ zziwV7nkCrsGgbs!AXP3r|1umE&(TjK`y(v6zxaQH2rX)Y*O`Q!fo-Y$B zs#aTF$pnbyizu>Ui6SdDN6nX+GZtK~zUWB=LxFTKWR99AGi@x8w4h8U@^g!b%=Sx- z2YVahvg?15cq=8$kD!MVT{r(@oCdsZ{`mW7!^D_VpIPc;bCK2V%tR1G5 z|K6EM692=uTtbrsoY=bAvc6_NLz-d{ckzhGYjWm)NGJ1}yZK>nCor^En$1fEf?g|k zvXv=GRt=1-{L*b2?Y5)!_shayRea4Z3(2Cm%o%>FiE@DHa3&6FJ5-32z3?K2z{6+d zVPsI-!9v-)hRV1^4}U{wDwZ7*WGePJVt3a$KP)EkxH39Mo%zDufii0OV84djF0Zo> zi@~+C8+Q1G;>ajgjFAkp1f%uyGAK`)G(q|M1mlmybdVgm=RA$%+cc8T)2zy_Sm2PL zdDKZNu0o-CsXOIwy}eg`U4*6xnE=f6GFhLbHnH~`OAoUs)p~pTPsezb!hWSR|C?~NkOSBH@)LvP$L(_NfA@~R1 zZm`Sk`YY>aEd8_6SA$(Bv`R$GJnBguN9MzfpP${-Ue%kL!YV(RqwJEBb(QwL3ush! zl3q`_^)JC0NN1Gn=A>U0QUMgNK2AYVt$H7dz3l5y6oKN?8j9K7Cui>wC}y`F!)pIu z>^_P_YCfEEDJ=9#2!dX3??lrryLrLGeG$F8Sb;6v%|F1?0Ly*SNyS-CzM3$Gko(_Y z6f)tjyzP6yDw{o+>{lz$2dpp_#F@e<+mCFHU^)*U)O*KpUZCeN?GKK+eOyLvIVnC$ z!N*vzO@Jj_?5N-9Ekl?<@5O_H*tL+4`2rjEX3m-k)YU+&0F4kdN#k&!(0)Nc z^gXbX$3oKl$Lx3V;;X!Xv#-3o`ZG$gw#$~iJXzvhO}2qXC?MP8q;7IZe79^ z)_+^kHRc(LRk0G{20B!Z}dY+wa?#GANsmF3I{kjsEnsQcP_*&~!) z73x`iz683Yg{0y&Ji5WEd1fTn#)5aL$V#gh~cv5a>TGsC@JFf!`r_;0; zo?BjFXm(Gw7oMP@s)+oo@3<>FvlWTekq&jVeRwLH2^riTKVJ4N-zJb8ArK3^tZkph zvW1Z|#X-er`JwQs>$gnb~DQo`U=g|ApNQJPe%jh0F zofTj;vnJkW|F+O$(VuYoH-g6AUm^OI-PyLQbktJ~70l#aD+WRzS&OL%4{`sTH_yp(h>;aI2yzN4Th48`qpYH>lH)Dn zh*}B}UeeFVPGRrioH?_S#8avyL43Q;T;J;4ir{vUK`a6ONKVmsF27GBnNp>+uK2(3m_-_1^Zl@no1#pOj0YOuH4O>d-56Vg)IMX<2?-m+B*%(L?aN5b|BG-pAHx-wD{;$}?j5906BiR*GYR ze}wIg-DQfe6ZbBa2aK)d#ksR3-U_%v$tO)W)ttSZUL^e{FHStYT+Nm=D~~LvWQ=7u z`;+w-(|B^S%+{g!pNS@6wk`};9-N+9XW6Tb%Qix!Cl_=tv5?)u_8|%JE=K;F9%cgR z=|ah`mP6D!HBtKru@X;8l;)`H5T)Jm?L1)BiMG64cTA5~WR8)5)0@H{O$Q|>$^fa> z@4vB76u)WJUqBbO*XUs|H?|rW`(U#OKVjo}R?a5xajs2!4F@BXhDSANnshc$Rx~L> zs4vDNWL;a+9})m3dLcl~tRpMEL;w`7*<`fH*%NFw5k@Dmm(F>=`n)1QyQ1}7XN|_B zv!GHDpxMo3P5u$7F+M@IFzpR>PN)WqdNd?BV|(H*wKHAv2Z{8V7;&DxKnRaD#510p zJ*yaMmnuF(H5RUXVf|LiR-Mqe1w~1QQ~<)mQn}r!;vU70!8RQXxjS1$QpbQVwuj;f zv*dl@s0CM0P+qHY>We=3z5u{Hk?ip*ORM!^5{%aJOTo!U1WiEOy81V*%I&s#yM84G zt$WTcEEu0Y{U<_K*ADTlA#Z7v3hdCx?f2syt2q{$2qr{alP>u55;#OyFT4?Ixo{n8 zp41Ybg**veE2m%%#rY18J)1uoKeA-aTfjEtgMV=mXD zORLqV#yC-M?^K0*daE+EgOpfopI1&y--Y2+hTj}Ce`oy`h3IxdmNAO`GF3v;LwV%w zLGVmJCdgE?XRZmUpB9o>r8e|!0?^8r$ESny5geq%deS?0?%z0Mqcx=6v=wC~mGz%9 zo>Th(4lIlf+ui$IrdF8nv}&B{o4X(E+g}BKn<(ZjAtzd&)a5Xj{g?_)`ul$@b$ymB zbnb+c6`n~yM@T=X1NSRv zMek|lQWdSkG%Sx{2ph1M$?pyR?==7S8vl2a{w1Ktjp|EMhhxyN!$Egs-@9|TOvg!c zv#Yf;EUUVJV#W#K64NOgXit_#7D~+g6RZwZ}^3p9s&Pqed&Y0(0#h^)zZ9I+q)3shTYM zd(?J4mU*^acdD+At<8Pnm#~6u9J51h#=O<^QO@jc`5`luE(^tv#iEpOOPRs=z0zZx zF{RgY`AU%LR{!^h{_l7F-*55D=TM>&fBJ{U?@SzG>RY5@>XaklP7!=KP+=sea-j%| zcXAd!f=H>2-1_(-%{}s)uW%%I=`dDVSr^BT+Vc@ouPjK{1{f1xfb(#A`5br;cPN`_| znVgEd%C0+I7OyASyrk#Q+~4Us-Cz5^KlOhFzN{xt`T3=bO3)s3hRfLQ88S6k)SxG{ z;Y230AtWS1)%~i}oi5sZPW}gyiCQ{xtaPN+4(pCcPxIpc{rC&X9E^Qm&Povz{}PyT z@!>?Sg~`fshkYQ_EU$2bX;eLX+a1PPjBqncUKzj_oe`|`5^QJMD>0$X=EdFW#V&V! z_9>Z?ilRJKd}o&SNai9`7*c(XKfpS~IQ#*mKlrEg`6p!Q6D>UVl4{z$i58G3RGf`D zcxA+XTYUM_93B>76gNtk&03m%=H&r!$~zlUeu3}x6*$L8O#<^ISjI?QqOJy15O*xt zNYIS9U*4FFZMo6ia)LmfczvnAqH#=xxG`NE$b+wHS^$GV#E+jDL;TE6pw~E8;%EM* zuA~|9N)Rm<7Mo5Tpi{L^6eu^MKeF7JG{)i6O&?Q=)C*!ir`7U{^zHBYA#?{yP$7IV zVLXPoe+=wYQ8zp;U5*SRS=KDY`_t=ku;1z;ANW%M7nW>?TiYZ6Y?vmsD9+p(ylU13 zVnafAbk*`+6w+1e_RkJkatOtlz;M=o_z&d^bN^v9POA`T7Cwd)1*9)t%{n^`jFWXs z@Fnx)m8jcVx&O+G+<(HVk($=1k*mJh?@-S{{ztbyKoXJ;#Vi};z!>`WKdN7*V;;C@ z|6F$A`6)Ut8t7K)A+^1Pbx$EDfW%LZq>45aa+4Sy*kh4}5-D#YCklbtQ)Oai2!iZj z7vdL;b>GpK%>d9{&$Zcjcz`&?7aOV72;hx}IRCPZ3nE{dFXETa;5uFRH&V9k{bCH^ z)?k-}hvg*TpksBS(Rzf72;G1)llkQ$>cm1dVkX0yfvtmOR<%mBf59joBR2V$a`=}h z3P`?-W*jpGKZRq7>L6cZS$X5fjmEt1^SFP}YvBrViqg|^%elM}5nx>ys?}mj$LiOb z&dp#kuBo`_<3$);6bbCbN#Gu`J47lw65E0er&-PA;BOG0goZPPY0l}NepPHWPx*w` z@!eKntr{8ftf&rJMuRmGG(Hh53Liws)fmB@W*xFp(9C_nVkLV}bXP^Mmejw=>PClFH}4BdVDxqR^Q^^^?dZNi8Vgegs@62~ejn z4;DVF(iYF~rDU3AX0JO|KPmRoN(g9_cFlC0fJ#U@fb@%Zx?`jyjO7LVg0P?K0%0ep zZwiu|8#Wos)>}30bJrxnzdYnM;qw5ljBh9@X_?vDh;w?Pdsq(q z@+YXmYQ7fF`;~zDEh8x%%b(Pn*O=nJiT1y>0Za2_(soEHgMB8xn^En%8P$H9X25sc zj87)^ZUi+c+$-jc^^w@rw9hPHL8ZPjNUNXltW^rtycJL9^ba5->M#J(cGh}B0H)91 zECG+MJYNW1Z7F16` zT0V>*efxv(yY8dEH|$f|`&Ug#GjWWf4WcFjIaFhPZK%W!QJvU9d)rDF(Klqcxv=wL z=H2!Q^5*6mNVl=@0^~dL`diM`n7Ij|nGaCyO_uG+?lD_P692E3!$tUA8L4-lW*G`4 z5vnvoJQ!<5_QVW_DS}7HpABd{8m<)lV!ouiL!s8v_foQoE1X8L)adTHMdV@ml+lfl za!9IFC*5ed4g6I3%vC3Ud;o?eCMYY@sK%wDVfm)q8J(g-P=;Q2X|WLAP6)4&ssZn1 zvm5DF^3*#6DN;y*esl_tWT6}{uhsz#{kWG^A&>X-%ldgFSXkg4OVjRs@_><&yt~?l(^g%?5GqtlA7op5xZusw-xh14@B`fQDCd-G88-mzEsAVpJEr z0_sw2xv4xPO*!haKpmz_@_N-e3G$^`)r;=Cn2?v6n7-aKjh2h}^&}BRKYAxIspJsu zFGxNi5K|u5eS+l0M(SaCoR=rkNc>l}sGlnmnKK%n)4$j#I;(5Urj#5Xhu$@{%J(|y zq>=d9k)=k)zamO08(z}@BNFZfj~tGl8A+2JtrsYb{1dj-YKc!4jf&yXUCB!p$uygYecR|`UUo*6Bhyb1+5Kb1 z5By&=AYvb3HmZF&JuQ=MvRN?~jWq7($>gYU!MyC5Z=W(2n2+4CH`PS5^x#D~~8)o^_}ev)Mb* z&$go|SN#FCG-D8~ifFpAm3E@T8YSew3^b~k1Bpo-9(8KTh@;}$0zFY%m4g&dWDtK; zqW%@kF!+?IVpO9%H4;gGVMOgkQngaoM2%DxrTZ5hoaK}Jq6MSJ8z~406b1dLqQ$Zc z3PoaA74GtzHT8lP^L+G383P=jWFI*43-@kHt3_y}9uq8ph8DZ=OcgB0ltzxCp`8QR z2RgG7k!sLAZILu4+=P5++5vZPzb9tU{k*C!Q%Lz^niX#8QJ#j}LpS*qsvUx|s{6E1 zVK&tODqOJ4p=PHk=5?Z2CGDcwben8ah#dmfI{b+U=oeG5=qgLhv{~^;qmjCkcUryh z7Q3>qPcYLH)q$Ck0Xpj3I5L`^CA%8uuQBhu^f5&U`Tr%BpaMQRhWJ4_NxI8WI%^d( zWOmdFAEdCwM=P&PF%z|7%ug2k{gp1}^;u)~r}lTv5m>FUcvek6>|Q6{+j)IiE|?p& zm$&lcsW9mPc_q{>7+;&k_;1oRmgGl&$yt)#3^v`wmO*Nxi#j&})=s-^{nn>s65S{7 zU^DwR$d_||e4kb{5=q}uO!TXfd}WYmjYgy=7Z^(?2ipz|phgKuNez@G!loiF0>71& zRlizNZOu7%Q6xRNym9n^f(BOmouy8h&pWJ3k3Q8NO=6a~K0dCdExsW=natpm*$?k5 zZQF`qx6WPK(?ilD)ffU&IpdbMWK3IEwa;xo+{0o?2sAk1RbZohqDB7IbgEAeJKRV% z(5uEnqe%M>S~eaUVx-`7N=+kGfI?n8|EPCnzH2^`+@4IMx(g7wM(tjA zyqIq)J8SkE3(gcdH_+uPAgSi&r|`@dt^~=zEc28uI^mUtr68f6lbjPLZAT(#n9h-rq%iKR#pGQ2fu5Q{<^GiMlpB7vB^b0=vwl zXUiAh=QFs&OspX6?ftnAgl&?%dSv+v55d~mV~t|0M)C;j|EKQFwZX%5H;T|e^F>Pltk-SL1z~< z>&F-HI0lnqcVf`q6SFanmXdEJoccJH4u;~#nJCK$xiS_WqNTd}wcb`mUy19CMDjB8 z`0{cd=8eWi+YZ7+SPh9^%7v2?{85$Y2^z^!iX^<_9uj@D4{YDh|05-Qsri5iTmaf- znFep4)=&6(u*9C8W4nrVI$@ZKo0uRIWKwlD)1-ey@~_U zf+aWr!kX*?L;oN`|QBpk! z%H?ep%If74Ht#*^1nG4DI4{cn(3SJL1Wd2(KF+n;Sb9|qxFZ~xhU@VRnM_XknFF+yv1QJR%J0dmNrt-+?xh-Y{9 zzQKn|j-1fvx&QwOaw}&U!D1i6pjj?{>yCJLDTJ+M;Ra8TOb^Nv#~l2c_jfh%@DV4q~c;PB(PQjduXnUuQID|HfXwtk5)gH`_XHv0|Z0Cr^LU`vc!>qKCr9M75Nn(V)-qR zUvG(;idvojBYr!P`!i%6_Q{iWm$w|hi44A_!^6F9`qj@H{GW3_$M(^as#Qk;_KyE; zg|9&#{OKc00HIjGBo{Of0UBRE&-YsYXPM`Wl*DCc=$^egVt47P^H$&Q#8ZPx3GdQN z9yPGUlAvSID4e&LVg2406d0$35~(ho5}qfUIODNaX)}u3<2r_ykDzx~8ii>sw0Dy~qN;1VltCDyaFNB5&A}Ko(@G z)U5g>A+4BPpBMFh2>SSi)I2Hr=hKcij(9gSOIKF0tbH1+``;N>0(^G5HA);u-}CH~ z<`tNJi8&zo-iD`d1S|x3lkO0QnHRAGH}Zf`@6g#pflSPPC^U-)I3z}}n|*F~4^CDQ z`;CabO>z|cY7fB1E)kE|FGOlqHhe#gt0{Dp#B0ZgaTGB3uYhv {@M!EYp{0QW# z)O}rDB~{&7({J3w>?_UZFjHup*siudC)*Yqidz5+Mq}N7kbOY3BkU>-MXpVKRhzq) zxFfg_`}Y29@H&~Cnm*X1YQ@YZRw4k1aY}^xV$9q@HP8rI0=g(v)#@I%qqnzT${yJv zZ-KrjXknps$O7h`yo;TYL!{`yplBzY8$E=EmP%I@&%HC!84Sr|r9a4_pFl|^UIgz% zXI<`IwOf?#9T7Pt^uM7EioX#(uc*p}+!I{4l5|k?-5$#ABx9*-!%4eNnX;fEF2Udp zXIQW_yXy$HR_Q2Et-Evg0(1Dd;nXQCebS>^Wu#=ZsL;kJt+{+HtbeV0S*E=3eYL71 z!x?$h9#m(q0N@pvcPckxDToEOyIVg5+KIy?+-Q5hkhA5@wu<&l^KL*tq>BLp5jL36<0IdG5*z5 z_dKoyg6Hrna1t5(9lGJkpNt)3ihF$a(_r?~L-c%qk`WgOliC~qn#^IuvWJShgP>U! z=Z#dM%y-iY!B8NWBg$vt1FakL2OJox-pjK5-OB(dp=zK4T~wjy%?%V#=RQE~&E~C4QUzx1q^blD~7fBhixcoN1NtMG^*LRv<%MAAf|7xl` zNY@iz-u;3qq@<_4gcs_DUH@vT`?k8mGO>XR-Do`Bz0kddTfIN`_uNb0-zBq;#wM(# zTTZKJ1}F1WLa0f}!Wl3YiWL8mF-PSOL-TP;LOj|1)LTeurB}a@z6?I7VRN4QnbOmbR)9-n%liIgcYCEK(fjR)7gUS0LHF zk6>Z^≫{k2$2$(s|b)VOKVJel9B*2JMwq#ExbB?}~}RN7MARemjsE=bG;^OKQZRG={E>0P5f zDu@&c>C&6?mwA-K3Hm%mrp9XCPG6Miq9=u&u|2rt-Ew^5&@l~z6Nip9QU*^mBU2C~ zXLKIKiszgRv<7D?eF+FF`SZEKbY37Qn3UnUUhBQQTmj8{I8)BYI}C}SOjh(#ILD&N z)h1}1`{%sx5&1?GczpgyWsuAaCNoVz8DK8*l-jR(Q+ep?8T|fMIPCoB8rg~{f#HY+Xvb7q@0!3%~@n5kSfBYVu8ny zbAshtbN?G*BZ5NZ@}SvthzHo7exyD-&GcR1<~MOq@dx-SkwSaZLzd!MJhkitR%Pq3Uvxs`WVUq$Yd3dWUzEXby$2L36ePf&l+bj~ zn12y}t(pVG2tjZ-n!K{3si%NyjF!uV?m_INvlete2aH$9!Qvq=NdZNF z(V<7l6eeYiWhXXI)+ty8fg{e9Ij1aMP}>|wzt?Ug=BamMd6(4f7|Xt5HOE1g!zB%$ zle~>mS=2wR(MYJQh^Wsq<+cA-w~?GNK$z;qx;UR{&V_yLyHL+C%s#>MX!9h?Pe&LA zOZ2Z2PM_*jPXataN>L|-;97MXAIa&Am?LMCNkcg}{+p*UCa(ZxaP+|^lJCl}BtEV> zlj*T{26bog`I=mORvuQ!SD^l>M$4s;5WP|ot{iOCcHm5+fUlT5d2*y}M+JGB+n5u} zd%ll;g+V+9g;Mr?@I&`HI|v~>6ZMgjKw}~mW}!PQ8CnCB%|J4wsdCh@Lhwaf(7Bu7 z>7f-TKo!+oH2YorJVzx+77~ntoI|@S?sr?QL2Nq7fSylcuw*eS>t$o$vO}b z$^xY9=4#Qfg3>_(KXICYVo0&d4tzTygZH+weI;58jioHL$_{ryGg!viJgPDySuNdA z<_M00k}F(cXE6oobM4(^tlpH^QYh;`eMgWv5kiS%0z-PWoTam{R*07%gk^IM;{N>olN+k@BI7s~2HxdOIG!3hj17_sXur@|X zi&IZtz9)k}CVl?mcB;VKV_k$DXzqZpbFt*a;(wN13d|tzl20U_k>h=J5ayz zUg(9!Y~FJsYWDq7-e++%yB58l!wZq_CYU}}*wt6A{F+|0Zc@!KqSBkMjLrjl`QWR`@^P0hAG!n`E~XbPMpRYUfj;e2F=lNOzxU z5)V#D>ny?dmeqxpIK%+zQ^T zGn<2DWCD!;5z`S-_(8KHSl!$GF!t|$V#EiGHjh8maWM==!#iw$7`D}^*&>yEHU%!6 ziyA~HIkNYR8W$v85+q92pA{yF0=ON&0R*TePv$hBiUTQL7T{2CYTc~M?KN#X1}HR+ zB-|kCOf#`H(D23FCtvnC;~b~noJ&=bKcC>G?G}?v{zDoo@9E?2@nT`W(Z8?*iFu)7l++2uB;I^d}f-Z`vxIGwpRI-7}rwiSnEdLLSzbjUUso4~hi#Xk+GY z%l*N!fHsKC}p9yg-qUCHWeSMH1C1c?a#303XC6rHQwH7_BY*tfc?_zV4>lv24 zAhbZfd^{vLCh`d7B&De(xNwbRKxLI&#j+=80%9w^I2Oj0W=ea%})#VU=6fknQfzFVVy29zq(e?`Y=SAqre0FPrpB$&oOT93XP0?h>B+ zMP|9*miLeg)48-zy&n)+A}^m78GgC^;q=^#&A}TJqeH=lFLClO86nJ3-zd2ADmsR| zSPSu*1etJey_U6VuTUq6`MV8H6VRYwvvpiScbCvf#7jh>MrsA#H`4BG>Em0ZL}Ovy zXt#IY=fg+C0rh|YPm!_}0rwww3N@%J2#>lJawfT}Ws+;Hv<5ZAO!fs&PEpiwPOX~r zYzC%mqhY%T#>2atD)sw_b2B`|6RkWzEHs->iaFPuf&ABzanQda!(tfA&NrLSmDi}9 z5f4QT5#e+!pPCrU{=;lO&2+8_6#|mb03&&j0%qgOz2-BrOAGMO7c#{{c;#r>QFj&? z&L=#`MC_zhG)}bsO=$%A6bbPgd>Qsr)u$wG*X$tPU%D99N%W1>8`6yb6iG2WqH447 zTge~rX@+nL%YCD;hEsC_Vq&1+js3%fhex>O&)dL{5`)VF8o^nS@md#Qu%;UM5FV&P~9t-ad5 z_QLjlL?SEguX?4&6Z*i;sA8!I9BgNI&VT{<966Ck&S4KdksWtNUN4}aGUI(gUo$d0 z;kRhQUk?IVQY#li%>0tj#yHHr(@Le@LAGP1PdRG2$=Py$&Xm&uQ>s4iNt!{PiJ*8g zpM^`$DxJLhb|PZHLP`))q>yYQ;+`VMyM7TXs92SsU9SvKf4e7FhQI#^@=14>%@yDTiXXx%#=XLKucfUd1%AnGPfmXi*-dgU_)RhX2jYqkv5WQ)93|Hl% zLXD4ZBv7)T;XL{!f+#x@GO@)tq|N_Xr$Ey6<6Mv=X_GRlC0IvcZY2VoM+<1oJzIbC z#KPQfCi0Ea??N?u@ud~~^CnO-M?SDGx*}OvATNa+qt&!WpN%g=@3hX~*-Vif{mk3G z^>1E@gcLzcCtrLW(>-Z&%zjC{38E65>tTMEQ@HYc3Jy?3jD?`WtdTUga3A4U^godR zdEGnYP8ON`ZjxUVH7d!c*w}~iKqgR6kX9SXqdNOtHk^sH_BEUo+>hQ7=)t2+OP8pg zPx(K*y$gI))z$Z($wi|^PpA>2MNQkWO$0Sq)I`u`WCCZ}iN*>NDn(i-;uVC%y9mw% zb2<$0R@=w6wzbb=txs!FT~cAD&2 z$7bq#b8O}~b_^cAM&{h91@mXjIv3_GwY;x3Dx`V;sS*%+K!EU z;I1FTbm0^XIkPy++_#>SxjLBvM7|G*;{k;F>U;RIUk*|e+SvJPcRATbA4e0Apk{t< zj*b*7ba$u_kmF>nNby+VQB#=W!+MigUZ{eW;jOYO;CoD!y#_uEv?{{+1O%*642EH* z2|aURkB!iQ=0wID!Ao|mHf@>aJfzOt-e3h}J+SrRX+YSuPJ`V_MF+!g)Th8N1Gmhu z1n~R8WrxC?{sF))59o^zugBbbu{;W1yR3c%6YhY*1pU`@JnPvi@7QG(7yQ`CCF}S$ zy{_m z!M!_$-l-KCqM7NR`;6(|bMAa@`t&a{20fBuo4`Q)m?X@o(=U3F85K`o2OZjGv-Y!z zUXA=;h%3*qUUaaEm$*wL!)IzxQ2)*iD`cKJRBE@6EF}^DkCBC)6`6HPk1ZK7sVrLc zy_}}obh>n`n(Yg}Cwp%V-}L89ByYA+0Ks*E%17Fsf9Nlge1nEihmKvo{aCa^fodnw zN+qhdu|?ybF33-1F=>c8Sfg%rV-4GDv5^5=K_UZNb4(xnTlh$MFjT zf%N6eFo!Ks z7$^{%O1PCeCXyOa_%I$DCxC$IA%rQmn!CusqNR*AnkHQ&ac3ENoUu_i_3P0kBU@ZUugYesh!xL*ODBhB@K2HAxUy+(+dJ)$UEOm$C`{H zk6rUp<9Yp!#Lu_{?Y#rt%ARFZhSJDA8a`s+aQsyIyN|5wZzu{QyETs{-|YQ4K+U@Q z3i2)a?QecV@XKv+YvJZt+qN%oB*3I<*J>WlJ99vth5rTthGCFVOrl{efogSL9daa} zbza^rKbUpz;#Qg(y*!9_TE6Y>H!R*-hEALhg51D6zRkt=S zBonsGnV2$ftcrA9CJP5;sJ7U^IcvE@LUY8*W9boQkK7T|6=@$)NfHwp%ijnY{uZm! zpp_=-qc6wC($`mIDqshM%BLRRx;Lj)pY*CjW|X%4p7MJazE~gh^d&jO4E8J%Y1|>| zxr};t9mr6NzN4|*3?}0Ch zJ3{OS=DiflRm|gZsm71x+!MslF=Y|?sx0hCEIqt!EA~EOAz5@-kSH8<`;Ry8qa zWU*m^ccCSXrS2?rt63(aBu7#m7Hc2L)M7%_hpJa|fC{zBBs3hgUNSt37YIzc$j{Fx z+)E)|50VgMrsJ&dx zL#c)yRn|x9BaZ8S^qsD*@mEOIu?ck?Q+~LnJoqmFRT%(nTuAS!aD`sGzaj0OJP{n@F{>d_ffHmumXTmFbo^yeUD;}4inxI8uHTn-y?mdQgO ztVrxV%$i>hS^|$sM(9N9*u4z@#9^c<_Yvfl2iIvqS3Ww+e~t#v9?yPRLlod{{_&(o zQs*#LlgCg*B*htZfFzgufWMqeJ2b~C6K~}jml-RXAI1uX?+~hZ(X5(1yK9{hAXjA1 zPTY6yI+-uvvb@RhKF6qZucvFF=}ZnpD z7_hXD&~iFw&u*ZT(AG&R`of*r73EUV6bxLB>Loeukf1w2a{E%O`FRHyY@V;eoaA7fyaOCW$e%@WK=5xN#hc6z(y#Xrt&>yQbIwwkhartohrl?O zu}+I3YH1RegDz(MY$fkO6tvU62Vy+E$ZV31!r2y3dYqs?eBYq=J9*Z_6g{jpFNq9@ zjYo+SI!^(f93+>o4i?4|2L@P4rV!JrFjWyv#YitW|Fv8U5mS%8gP3^eKh!Jz)2`I1 zC$;#Anjo4Z+K$qyEBX*Vvnu8q7fEs+cM&FB_&XvN+=yO_gHt8z{l zkwUq^&9o7zlFH%f=#gaD9NF_eihg5|U>A_Dri%AFFV`b4+(!-d*l`48 zqy;&H+E{;-EHMJ7KDfGuuGsAwn>mQ*bY#J3DnHEk`c9m`93jUgq?Ugu2mW|GF9RmV zyA&B}-iu_UK}PPd=C$jI7=vt_QEP6-lxr)+F+f+C4 zLyaOy-` zic+Y+iJ#b^O?Dou6ZglQ#AD~tA>!N(X?hNxEi|>Gd6U97qn@wFv_BN{SyM9J4JPo+ zPTgGU&U+G3&F``uBA6z9tVo6>U3u3qoh0y-*^XW6tz>#@%n(SB)KWGQ(7ASMWNE6u zweYh1^bH4mGI=}0pLl1#)%+>}(Gwr#HeO8Zm#J)$H>+xnx(F+lI?)1=gPt^N?#|;u z{w%6zT=T1!Hl9~i*tDyrYFemidrj5NxlLPE;vLrc2E~=B7_&%E7uDBPRpM67N4a}m zX{qm0@Pci!yd&1FYC0iczo%y!pQtGedH>DRq-d(-)XhlcI_v6&N$7O{&I5}K%XBhv zb5X7}U#u-Cp%b#e?eLtY$GodlTDssX^mUT$hhXpen=NAx-C0#nxDA2SK{R) zrV1*qqc`*AepP~~kv!Y95IwvTDfG&Dz%-mLsJ}Wd$9mQ|-R9a;)ro$oANMM?z)$=| zuf(12nnvxsHM>!-`iWkz#Fc*H&wG9TKYrr1y%O*D6X*6yyxUJ~?N#PHKMO>f?A*G)QwH{j(mc~YhPqY@wj%vb! zPOdI&+A+Brq;n@%Ps?w5lMOX!hH4J$+$e6=tLf<-nimWl^h0BJEsYQgF}*Mb+NCK1 zlw6pf`eu>#o+Q6Yn-d)K-25|aEt5b&S6D|2EnesuJC&ZB(`q?*afc>hI)Apcm`&q1 zu#|4guW7mAla@;k*wr0Pp7(9@zF^5M*(3ZBHZm1zc9zpcb(=t!3t$%2!EDLj^U7qW zo~@gKLw@`ZHWXu zyXBKu_3EZycyp*Jb=(o4y|*J^1~b&ibbbxtb9#W02Qc=xTmk|J81-avuf^8SD19;` z8zNhPYp_0wjESeuKeLSkjb(7+__!d5agE`t6r z@3j+QC)ZeeM9@OT5Gv%N?Nd^Sm3N-z%RTZr^i|~mtFpH0T&sCL`8WhNR44z!r78pB z+p5ICIedCY1i4Bs+^iPY#5nK{r9{Rd*r+A&v5`}>ub2ov9_R}d5h%FCWsUvW&@RRT z@615J_hkn8J*z&oCOI8s(|-Tu>`s;QZ@HZs6z--on4qTmf=W^TAJ`>}3%KnW4;)1yOqowWlF-`oFRj0jVE5;Oan|Q{a^~7l zx?*q}mZR(Yb?KS;@Lsly4$VeYW^j3J)frav2gs2+^SQ420Pt4J8H!d~tbH45IiF5n z5n(nliMJC3j1BKaVSf%YZCUcL7bDogP!#?rW^(hNL4|_{MF3NEnF;NUXEKZK)M4t* zq8W&eer=z&Kc;@pHg}nA=!Ag9+u+y0tVcU5&^7JU))`=`DV0d-s zm;6k<(bt1t<`nbl z=d7fB3^V=Bd^f$L7NiwAppG zx4|Dx?+SDXqBs@kdAW+>oXoAEGuh;&TD&J+@b_t1WYT*9b()w7p5Fe_wTDneo|Z3B zra}{mUYx(05R1L}u5Hz_W0r~5d3morF^Z<01M}a*n8&jDIY!32 zIyWbX+dQw8yxlU7cLpO*25V~e(4An=!KZ>wCuAUlrD}p9k->B{rv9f}Y-m>VZx!ET zrO_<@_$vk-2 ztONB{a|IM2;hsGVef!BG25GDunt1pbu?K65d&CVOd97KG9K&Kag4Y>9lX|`$D~G(6 z&i5Z9Jo>cRY?&yFun-EiPZ;XV>8PxXoDiWMgC)k;m~&RTub=EoO7Adbqs~jxys)EoA*;l3Y!k2+>d?{y3OzoA9!kQA1zS0O7Dg-r zS*?Y9f%Q)l)>`-ye;eF}j}Mpy&9_fRSVn}ZL31pYT__>yt31@I;W}uRlkBkg-e?%y zL45B7J`ZFY=M@}}v*g1cotNPs2;p!Gh+j#SH&W2E0vSd+a0@KSLYn&j7SfpT>B-l> zBn+532<18Hu^+owq7{!&%p}#=8rp`HV zQTp2RDFZHM{Fk+{b~9f{Maq2Fnh7_B{Z>UF5)s3VTk+e@LEZYSbiu0kgY|hkZ(uzW zh!4Z6sIm^5d-5bOP&=I_{YJ2uQO)V1e5=VLN-t>gE*?cBZ3pW=T$r}&=?#>i3#@yt1EO+nXcjWe>) zgsxzci4;7EHZswLUqG+tTn?bir5jM>HV>MmoNt~02T{(7;Msgh^i$Fy{mmLQkMT3C z#TP;{& zSg;yEf380Dv252DfNDwjDLIx7<`m^E=)g5af_S@AA3DHHLzb0t zdoy{22O@#+V{Zx1oN@y z`5W;YU=?{b(x)$*1i|p}`Cb}1VtH-LWw|(OzzIq**B~~J5g&l8C7Q$6?Pu5N|3IVIhGQkm<;Afq1fu07_8n;@bv97U4-3QBYKu-TQM}wU7#s}D zzNkZ>YUyO($0JrlA4^X~tR8~{!ku`}A+(KQ9e$_33^#?u2HvefjfCzNU?gzL^bJB* z@`!u+NQrGvcW@s7tTs4VE%`HXvqKes(`ObxD69Cv+k*-o^D8irkC<5f1THY;-;~vS zc7Lb?MqWEa<74TO#)0CO;s%ZnV1LwlbqAk^5Re?n?yQ@D*?O_w7>k4$vE2o#Xv-)a z$wFlUTGv{MKlZ29*}Pu0vbmi)O<%w#^ZCk5o#2z-In!cq69Stm@0{(ESVTcc>>p9o zN|#Y7{Z$oAriS^m^SfE|$NdShNd_|^QD#Howa*Vvyr@&z{A$zTx-U*Cy_WsTub3bl z?kc8`nQNu^i}#3ahNA?;++A@xJQ6F2NL7XG#I%8RN^dgf>;1b7(q9B?7Q}4zZ3r58d!TSi^jRViE+sn6dC~1a-Ne z%1-{?7~DhpYruISl|k&7qy4M0PF2b@sVGS~sjKsyP1-qKDU4?UNnWY!`WQ(9_Q0kJ#&7MX@}9u-wou9R{k+=Qh$~hF8>Yp zFp|57Gfp}9blyyIEryBADhUgu=si@xN-ioyMu)yp51D(d%_1OabZA4qm5}}+8e+ah zQ)S)?5XG#6Qnlsnwa7Lq>5f8K5o*ha=2>Yx`iV|3JU13 zgEeqE`IP=#N)xq2eG+5_b4>48aIiJJ@a@uQ+EnM(ZDJOm!e#|d(FB-x~? z$*MH#)>=HIpI>yOWyq+YYz`;N^Vmn?_mHSmfIfh`4`l!kpfj-Vx<`YN0k`*KJ`C!f zr}+muPeH)Rsx<3XUDv`i4{!RVm@qmbEzxjp%L>k!E(+I$mJdCC`fJ{G0)Dz+AJejX z@?{iOlCsDAg6VxEyJ0korbSaWk&T&lgiu3Vf;zZ!`iVKgPEb^(iVvx(dQ9Jz1J69; z5k!ofwNa;E=L6Im8<$nD_mizOCbTspHCT(M;-h1t+W7|%=P{83p(;p6VRBQI_cPy4 z`N`$bmJ{JzrVL*&$S&gwWeaWKn=$MTUmrQ*1y83vrS38Ffb~N&$eqwhc1HqSGs5MN zp?I+N&CH~_iO&vRWi2i_hJuE1hC^Fu&w37yW670%Viri&_HJSN-t%plEs!gp3_0?%_RoJ}H-m zC}JG$@D)257^0Ro>LQA+^jFUF4;S82_;bn)4<@t%k;7P;Bf2SlG3h#3BRQ;;W~K`| z2t3k_ncmHq5i{#6;A)m~ipns5@cXJwXz6}%3|8u@44fyk(iqJ6Fc({flmxKa=`|Tx z-EBaXGSZg!@dpB5ucWt0(%P}zRqF6A^^wwSs50sk?MN51ALJclPh;^$O9Bf#i@P%x{6O;E`Q6F@zU(`s=J7eA0$5G6V}FRGXDJ zu2AnT;hq1XwfG1)V2%0iJLX;Q*i5SOLjcMZSWk03AL#YnSkfJ-sXyBOK1x#-xLe%E z{IA`G!S&bOrB%8=Jug|4cV%>glE%y_!mFa-^>Y)7nPpp6^8M<11avH6a7A z=+L7`dcP@HiKu^d`;~J^SPT#HnZTDtltdJWgor zdUNn@a+XJCbro94k8@Q~K^}z0JAe#gCf_CKx#F~3DlNQL2Cs*x#k<+vBZkRnc4LYz zf_=PYEqp$jo;{1)gd8ZmFg-V<`<8*^Df--}yu2pFsq0Ko(cXp6rytiBA`Nq^&9~R& z`n0(&^Z_jA2ar%C2X6`V$h2@FHzGV8e3c;+F$m9k09Dk#wgs`Bo8sD*csQh_<5iDe zqSA6>(3|4v>%BBRrTYz##-3Sp*4skszazCHi24%P#7?;*W>yk)&L@8CBPV3kjF0bv zOy(otpdGiJkL95-ai^V*UY8^9jb#2?v{J4wZ>Y8D`@@^L&LS#ia!aw;o2)^K0D!a# z(Wse)S|&LjH5aFbBsa@z7&#WfgTf`#(A?4A%5LruYA10QiQZ6HQDUF(TIv%3>!PLJ zSCK!ZajV2HT9ff!7QXOF@POzr-frf4kIa&)lE|ZA^Wy712CTqk3z8canGhiN5c{Fk zTDUU3WEMTqs3og($BbzC3OS+ZpOxiG9IVKb+nwi8Q_K#Esq~9^2maeKITz~9C?&l% zIFo5V)2Klmco(c?jf59Es81k`!_n%}CzQoCIaYEI#BDH6e65U!4VvQ){2t_!yJM_5 zUG!F@`7g{7P)BPk3CaHAkJ~E$Rtv1gT*4#+dO@Drwnke}OxJEg6Cz-UN<9P`n$hXW zqD33Jl{3B!01;Cu#!CK(VOEcM!>)}jZF>v$tfeax+=lZ(9H{(JQAF_OkKjCXp&SBv zE4=}=8b?W}Cr6dF@I^vVbg__$NHuzZZJ(4Bt=O@fa)4*;ht@(CyR7@Pi#CtwBexSa zEyB?$Gu9#R&OC~IL6O0ZpdD)vBKux5%i@3^3tja*)6A0Iu^;_?q$?K<} zMTp!!Av22jiVZSVm3nW`eKRiXSyew3_Nm88&|?+qCgcNq+q9%xuGV@wc@{;-c01%H9>rm?#-bNLjoRw`DBO`M@tOeEeh3Y2K5p1p`6?#4ufn(gZU{r zi*dXCY!#S)j?GGfoeZo_KH{$}{ry?wZ1WIJj?`TwJwHewq{ubv>!4|dE|KG^RT7m$ zt0U=_AWMee@$#BgCxuqWoi|zr`*KYz^vTdl>#kdvVcr*5)&+dhu*esr;7B|z7RiYJ z!GAIg(r90O_i%zoHh41-{G^P}GUwWkG_B8lrpd4yyyp;ijpZD$^Ach zttM9l{HtUNeGXNb89#_6p1Bw)ZD&Zh+X~ZdrG$nPQLxXj5+Ym!%f1+N%Zl-2YGp`k zp*K|9J+5MknQ=WCO8l9)L;pDmtv3P*cc%4+7VP%xK+_R{j8ca$)@86Z)2+jic0RL` zKIL~q@N_E4z-w@8rqS|b6;(C;6D`fuvqg4ALCrwyUziC0sSiJWA{b5)OoXpNq630A zlz9kEvmtq25nY5E{A5%%ijXbAgB^j$~{l> zYw&hR0>p#?BJp<3?VF^5p16V3x9vDG@y;pEPC1M1#Bg@i?&jhP z3+=WYWfeQuxg3VPA@SxZJgdu2M-Va$gFgR>aO5z{O&}?!~JXx|mhgsK4k|{$H$n=wVl}CfmYjuwB zeP2%x;gIRQ*5YGC6k~KWLL@b|_cd+vKBUylM`D&`QS9@x82UQt2=Dc`TsiC>e9-s` zqYBK8IWP3y_}b@~kQ^f7g;CVtIrBl&i&1C0KwanTH_%p4Ulp(LCP}VZ=(lk7nrH~2 zX)8f5{3f2X)7NAU??DG|q8;ATs{Glp((Lr5hP(U+FW4LGheBT)b-n?6Nr<(W_{aV+ zOCFJum& z2XLfLIyns6a(-Bosczyk_QdxtP7f)HhdOW=W}*gh0awHO&+_8}o)xd&+4QE6%^aZ} z+E?*WZS}`Zf7a1b$(NBZG2FU$r_txEks_o|bVg&1#A~MK&^hmy3>BohA*Y93d#jL_ zlom2+-V;2!Z_Sm-{atq;WkZ?IxHy|KuiVIZ7_rvf|Pki{3TZaBV(DNoOLooX z0pLZ|r}@Jm&Kb7)dHBY`fG!}FX17PHcUtqFG=#4SfU=#_2WaRxsEc4HM*l~thMYY_ z-sO{*;3If?-!pjq;~JOd=NgZM+ptj(HfYw=>prs4m^ZzXf#-%u#Y%y3-Y%_Rti{hq z(h=Z0E|Kq@2w+81D`Nn*SawlkAO=qe%oR983Tr-ZmK5Av(zhowzJ_c;Y^m(F*Ommx zu#f4ePO2l4H!h$$53{w-r)1qr-)D~RGfB3-MOV99WOfYi640sp#a>N1(MROQrw?k; z?;<7wfV_eYnnKW#t*2=F{4~1VjHQ5ZT|4TWw<8$RG;Q&udDoqPY|!djTCJ&VSHIr0 zxsCv~Eb*r(lrhsApiJ|=$8hxFL|toRu*Ftt>?;p<%d0Q*Qh|hwD2Y|OR;r0itZRlP zdqwr|zgGi2hN))N013~kLB1{-*?yCRO+)04X9A(?@HWtG%_>bV$lS&ldySJ{={vjL zM{m)4c6mP&yzWDy&ad(YF~U8snUb1+n1S**0jY5)hi8P0l7%4NXj^g{-%hS-e8Z?w z2w=LHq7myP@$O;MNl!7RjC9di#-xW@ILeNxa;y1#@@KwX{f1e!mp8w9=KxrO!aw(8 z*AQEeYSY(#I|nxNwwFT!mdw*n8=h%lyk2ahLmC_G^dZziEG z@V5~CVZGnSD1B^2$~Qa`zY|tSu=*;fqs^_?fzEqFEJ19>UxBzv%y` zcwIg4lH65{y1QVn>H3G9xzhvT>UU_Q_}#nMPTe>6&}R;l&vh@c^H5NcduSCNR4}NSMcc%ro&0;+Yw|+@b7v6+7F# zBhb9Y)7SV+W~Sz&GLoxtkk3!#Y7?jjz1J8490+Lxzmyin!~!%ry8#?>ERhi|Z%zkV1O)}B zjoaU5?wP=+j{TbPm^i7HtDPzdd(FH67w*bS-_XU;M*DA?fr~+_RsL72{FzAA| zcM*JQIe%;QMrpgOajSi=gR!_4H1IqKH?((KTZ#x2AHOT)=P}+7J?}`@%iO*%TS${w z!y>QBntuZXanCY{EgIazq0fHnwR=?y)N%>gfh&ul4j(hLg0d$`L;Y*I!0+GpxSIa8 zsDF>emmWs{CMW3MjeYv}SP@=xu)A9_megPZzK6?qXy4 zAv^Ue#Z~MvR-#?`bK)dId=(BId7cgt|Xinh9-yuJIQF>T;l>NZ<*1R@QLbCn@Oeqmube!kW^Nb2t zRcu8Vn|BRwBMx#;UtPp(fh=po0E;UED-WC1_yW!ve3AGV@%>Lsvd9wRZRA8M)<&vZ zt@+KGhzAw6ogBRjD~^U%3~p^HNobJyvgv~*+GHN(1i<@z!BNt^5ga8mO2?#uZ=hzr z6mdgdqBE}>nJXe+m=&F0?p;StiwTZ^Hk_p%-Raw|>1#7f|3-o(Wb}5Tur;5lVHLAI zj0V9N13`$E;XehL%J2#y8&-KAsE~Dr$`cZ6i7_GSd`NTv1R>Ne``Hcp4fw5bEcb3? z7Q>NV7@Ezte!N;z)Bd~~>)v*bLDyn6x&~zN$Ol@BCA2B8PMDmoziQh>x-K zxOBdEJjMQN-D*P8&d7FtmyC3mF}NMvrLAH=CJ1be7ovN?edjaiNSP^;3`%H)QH4f< z&ot8>F|{7y-zeOsXmF=}nt&UqmYc4~l5HctZ0k$JKp2e9$f4c?YL+Y-zrVwX0Y5%3 z8=kRMNPdK>0_*O4IfE14Lw;96D?5La5p<@pnie)T=2dx@%QSXhw`SR|PvTH!#Jy{g z50#_4MFp*=yK;i~ z7OzHGbl+RmG;Fq-pOwYBww(TXftGs*6bwTU3g zT)Db1j%t07yKQ+_*Vyss8_X*3Hx$f33`p!PY@9B)hvs>+Ve77xB};incwv^53pt2; z;aYp*YF4sEI^NeIM8+R)?{Qv#P{r_0!RUxjwtnX4yIqk0+;B9FC1zQ!{m16KVP~J4 z$M=)ih>tTQ(rnYnRKd|_m_Vr~5YYu+$umsdsUcx)4&UT9KEue;)bj2PoK1u5#BvBC z0Wu@wBHxaTC&b<41u7D`xU2I|{{E;)>M-vdp55WM8Pxdexw%AD?~xI2;oX8Pf1$`| zLGcBnRp6l856$_Keo$K6^9#10tO%uSnBXW2bU2_myC!x3?R>Lm*oFe^1sDQE!8(%{ zQ6A%c2Z{P;i)ip}^nKd8;k(l-tws-G(0-iA$t#g;>21DLMHnr8dx%1CZYB?V#Z;ci zQkpAz`Wh|}+-Nw)QqZQB9d?>6H97*77RkJuDPPYn&+anezfmtXo*bCao`A;j?WTc? z%qN%_-Tk)+?0v%C;)&-+yJZuA5B#3^zh*z0cbg!-j&B!$j?ON;6XHxs5VKs`kW#a$ zNx&;|w~@0|CLPydNDL(ls;ay0%h)kgNE!Lb`qjT&g6a!KF+{+^v8C_h{^$3LXm> z1)5Be8T@i*h?N}#i%dM+%p6_j?^pbdyXYn#G&+B|OMCNUY`4_R8y%bI8k|XNt8&M) zL5h%W1cnBcg)g)K?s);H=fA3>aJRFu!Vc|355P7fGgO&)qTqTdq5LI?C%>F0f&A!M zTv7j)ioW3z-ER^^B|Qu_J1$%Xr#nWXv%h$0M{w>}6!xX(@oWfk$AMz3Gawa|#2amB zA*#aPnpWx*=tyW=bm+C=^4nWXW2;xp9BUdX9S1cPpMTw1Ov`l1VRm}R@8|{h`FB$V zd$fY~T8ocV_2@~8LU&QMYnHInWgTa8$~b#K!|f@)S=AoAHU}h zl@ZY|okZ{pL;2S?-ku@Zi0&&id=VmLM_6|e!M(_Dlv?Xf$A`A_LBE!j7>bzwhtc|O zQf`C0#p;81JJg3Jlp_RdeCA&Y1uo2ze+#krE>6` z=?SJizwv*Mek@~8IE-qEAjm)AY~}wZ{n+)d(T~O%0sYW%^e-CnX{g5RZCX}{RbPk} zGGNt{x>@x*hi27ZHhB-ts(-UUtolL2c?A_%^@CZg`p-n4`6pO))A&QN>8*xM2V~V} z(|*|^t0MXCYoc(FRWY?57E=lXRzEeth^`Q2yx3p6x$}4Kxaoc3uLtM3#+)?*RKSg& z(zN$EB@A*<5bxv>Q)R8Q!o7@A3nRYum1Kr2Q3d=c1cUP1Fx1&4jf zh`}eaWe8IEZJ!i2<)n)~uPvn45@@Tp9BeiJx4H$nB|JbD#r5T1XQOt!&LoFOscuCH$D+#Bn!dR+*>T`rN(qRWj>fxIl8d(xH?ide~i6jU~;?}~vC$J0V zSaqC)P@ChveGP7{rZFOR4c%m%T`8(Gv3v@a>)yo9r&lbVW@%yf^t2XA>o=>Fd-rfU zn~BcQiiexoJBMZ5938XCE#$mqHTnVas?mp`G@ub`av?!gX|Q54?s4mBl<^>f55!W3 zk(8+|Q_&gZ5KFHdygq;m+<*m+Tl))Df%ITJkSnd^C`h5|Dqy>i51qpaZPN`J1%@ED zjbPz@Ai|iT$7)9h13=9Q_0#H?Xysy#W0Rmx03cTI!KU}N$<_w+0q*gmRatA}w}7SX z*LrMst(>5q8IbQ&lhk;njiu}`HLj`Vux}ax(Kb`*d@Ac|dV}+$ax`Avm>``}P1wqh z;w*3RQwDgMCeogmRl9~E=dvPbu-34?9>A=Q+~xS7kx?-q9_LnjZ14^2iQzPg+Pdqo zQ>O1hJ$C9Ii2#Ja88;9j=NN$0rAOq)h-2*#LFFDTSgHF1zX{K(?qZ3Cr3(CXu9V2# z4nKvFs$;BX#W~Z8i;7zLwvJ=pl6P@KRVqtCpH^|Q=ipTUl?Wp%xN@g;@Y4n+ds%XlvqMJ&FgK>1?1oXs%sx^7T&TsQp8H%R%(o-nPYjs3r;Ms z6SuKHzQ3JHnT7-#n#Nf3>RIW+n;U}Mz|x%t<`|eB?tV|V(y(y4X{LnR%DXO=C&nqk z8Kn`eX;a2!&aO87W&;apk(5QKrT%I#k(u{=WXc&DvgQ_>559py*Z(N60F&9$%&OQR zm;HI?nT9Q86>NO>v`WKGKgW~QLVPLC8^9Rc$^*L4vyd#Tgq(zQ1^h4Lu;PNu1T|7KjG?J-T?s79k+^?~&;nmR#E+v&=ej8x zcbpS61LuOcBDs*iYn`@vmNC^7BOwEz-gC)np3Haz^T=kzmzH|J1Vv-%d~#-{ zEuqE&6r%vIN`ss(>aX*Mj~Ep$?EEEKrDHOa)~`bN=nXX)yI(QSZ|hai;d<{kXGx)2 z!i1KyIE^@XhI;=+v!bbQ02nq@DHf`o{ze9kj2-}9@DC5+X?~1)u=5EhQD=B%reA(5 zeBYP*KE3P=-H_5g+Ytt}Hh9%^Sq5F!Ik!=?fw@8az&rVDN*>&bBTykU752n#v_xwj zr%%5pU)6L$|5LK3?>(5P+-twfWW>F=t#hHx&R|hl1KYo5(UIU}`NY#ASJ`v}3-3;j zJcnTsKqbmsHlYrJ4lK_4IKwS^@f$oRDtwBR9}G}t!er`t?+|r$M4ab`6(U=FWHlb^ zbWj<~;Km~oAC+-X4ON=(8zMR9uJ=jL*WNWgCdCrYzNj|wZlIr5s0pTA&Trd-FUS*rhMrJFL{Y`)xUqrH>vu zMO$QB?TPubv9xw=?$gS$gIZa3h*mZY^vMG!tr-Ggnk8BnG}fC-3pI9c8WXanrXN`5 zAV_wIkZA6DCBPJ9RI7i$*RVQ{1~K!)j~0NNQ^i{ECSLhlsjt%=c$(u)!*siBERi5~DHm!z4?8b$Mhwhv{u{ zmv9mQ)29h#f7XQgTpyXYVCHZp%VC-N_->Z@jww6^aH^oGK|=Ew5_~zshuW>(&qmhi zJ2IXB=pP7(t?l}Dj;-#cLDq477<)r3x=O-29nTJ})>LFxZSj|{DB8>^ zdB7+z?vDMNtWb@oYGqqC5Ico*puNxiD@IwVBgmF5SVBv?I$w8_<9hVn6L**udQJ_4 zHhbG4&+)^M=JultTJ;6z_&Ec@3{a@kPA|}xrm<44juX?sO98>WN+{)9pclQaMjB0H z<5#s3#y|_*{M3^QKTnG@XZdB$@hhXvoKv{oZNFTNpGD!Ug#K-K3cU^jW|3)UBQX3_ zU>=(Rrc^;bJYR8#m?qWaIOqT1=z4KCD?v1FN7vIQhZg__j3oTrf7h2IgNoKYE;mqR$wW`sai_mfENAVdZy#ore`t+4 z`*#zOV9WPgqdu@kZ?VdESeI>6ZtKi#)|`IdGOi#6KjRZbJ)AS}%itIC$S>!Jy9)s> zULV}@E2`;iw8}S2ZOk*7t$xGtEg8FpYYBgq{3&O_P)ckcK8OuAPg~_~;0o>;?{|5* zD|a~_;tk9Ad(|3Ka5$}`oL8(dCw=l=-I{}$2XcK`x$z+BcX<`mqWE&YD87Kd>+{=3 zvMHXq?(F69yQ=oY_K}3HGd}A#(u3k}7PgP14N4gIHh-ITMXlmUzJ5>ov88IpZK&0G z&rz%26~nCZ<)(pSanT>UTrT)T{o|Iu1_Hs#bN`Z7TGTnHO%rTmqYH8|XiTdJQ9QwGUvxoOk;HgY$trA4~wd0NKPCF}Va zuj0mb@bTDIlb@^}H~lUy)8jUCbalUx#U!hg&HQboO4AzCt~Zsfou6^*=+LUnyXREK zn6O@MAnkp%ZVipvtduuNSpobnF5%``{A8Gr;yXIB79#>mqj^09`HBxt| z9Ygq;T7}4%)XpE>nKR@xDSg zXIkZJ47SgF-&(n9vsFS1WuNVmpnt72@%pLP<=2C0vG9qtwSfiI( zm$g~Pjx5d^|NoczMo?E!A&vUX3WNTuMuJGt9XVymb%T{Wbk8*0C+fMs!$C5Oelst!;37#Pc?Fbb_Gu zG{kl;E>0KaRO0`4p`;qAIJC5yyAi)Zu|~K)gko< zUn&Jm3_8`pz}7IZ#Wu({6I5&L);E}OP6;nsv>s`ili84 zV*O{|PD-{love)`RtcWn{a>)q7vE9NgNj#cvL3 zulFPEx?>f1z{M|LG6maQ_>G6J<+m|BiP-~f`3ZoI<0ZZxlJB2QxZ)9cg!WL|qW)@XU7 z#cB5_Tg|`5CPtoUST5`8X=d4pORB!TgQ{`WHBAjmEyXf>obPy`;XAdC_gF7}$d`Cv zooLo->EQ&>TnnHE*@^)Nu&R3SCzAMt-A9X2j&ny4&D*)dX?74kvgKCdU7k#HYM7UV z4IIg`9S?!Q+Zo`~4ot4K-X;3QD(nyV8uM#K4L!QKsp-JaI zc^Dml=kd4Ie^o+Ygi=XGWE;TFQ#nC+>xCQt z3~%HF{{!lugop~b9|=nZt~>weEHf->Dxch|H^QOfST$Q|6zm|C>(hmeKF~Q7&j)&v z*c$>fe+_0L)tCvi9P&Y!>CEEAP?9IZYbcjJE$B0(H{tSv5+Enm-EzWk_Zii3rvGj9 zUkSJsr2Vstv;cdQPw0O#`~Wv@wE&dzQ)$KhCY2liq;qcOniIIVdC(k9NUz3)2LKNL zdv!PSF+*6Q=X=B2s7gkx4*cREE+91T^XHyMPx5@eiI}Fy55@-eM}2~D2$j?Y$RQ<) z`9m_4z)|PAfJ~%;E4)SfJ9eX_(gVC1yVI-;V%BM`JT^52WNc_== zvacv*z4C6%d}(Chzbd=Z$F9}g`w{ERzujoO-E9&XQV!B96X~j_Z&ix^Y&H=$KMGj` z@5gbnLzpC{Q}P6ojH&uPOh4YU{E*cmoCSU5!(8)W@>e_s_?ww~GhU+kcoKODO3*ir zzslrq{At$ypYHsxn*34kQu0H$c@mXwsDVm1)F{CD_~qVjVJ<;Edaub-BMM$Kz<;0O zz2Cm?n3e|ZYwlFz(TJPgL4Hio4EL8KpwTu_golnSY{afMvQQ_k@>FJ?fDq(j=*TMb zR2A@B-yc=P98!KC{q{H5q>h&sx)>rpBv`}ylg>`v#KD*E6LzS8tf?!?;#7fyQk>)9 zlMSTBD^S6k?IoL3Qki%`q5DuR4pgbBRduPUaOh<*XF2XvvdCjp!Xr9Xz1`OiV`eJ1 zo&0dqcRg(_GjN-F7&{;HV~ORH*qaay-;YjZ1enO}Bf4dW1Gi7=w$j|L(QS>nMW~!Q zqTbwYOO2Qip)H@ptG73Py^dB#QcXGbHM@}uR>rH}YWk5j0LE#0jZ~$!e(K!|z92sy zLh8`4njknh>6Fp*q%Z%*00;-D;H*N7IcQ4U*^KQ53d~^;Xkg0d1q@<)ykaL7Qpif& zS*k4v#pGa5XT0L$ry%J$?hpS2{yEE&GE@qRM1&z%sVF(>nhMwCB4&}eMX`G&xy5b? zz`k4noFI`#;>?8F3LW^5=_1;{{rEpPV{BEf0E zrm-a>^KA92Db}B_YOM+%`L|SwH-HG?1-L}8LCVI_XA8@V&o1_9|?~t&2D&-qdHbs0wV4{%d>^(`> zFe^}G(9@lD>3qa#hOCi|fd|SOzhLB2CgN1>Jw)QI*{lc2zcoAK>_Gmz@hm|TId~Oe zhkE95y;cD>3{NM~wu-b)_(aotCoE7;Q?~Kb@OP_Zja7ocU0m;4{k7t=H(UKK+-a4( zXq7x;^cYuwJ6?i!+(*wl@)%UoyaO2V4kb(l;~ks7F`CQ@l=W*=q;#So(Lf0JUB5k=i1jHWfXsNqkg|G{X_AEn<@CaJNda)# zAnG*S*IskKdKY!-ABZ~kgU-#g0LihCcHFFwhlSf~9#IYw>_;iH-_48kJ8emOjrgPZ zV!tV3YyBpuS^XM1+G`{Um>2bGS3g_r>Q}R|z2-IZg0JatzbUk)-vrv#uYnf!yZM9m zn!S48Uh|1A@#;6MyB^ar&e`QX51kM2O4{NHn(Nb;)J*1@lgTX84)eT5#EKD>{Z;W| zZ8Mohg%J{`7IY%a7!!~o6Fn3BEz)V8nY}fACiocKlnyz;cYF;O2>8t@b*z24i!@>T{%f5(r^PgTyd+S~(a9}*_!a73}GhK1`= zXGWbjO!VOccH3J8uJ)*CP|M;Nkexj4%AMiJC+vW zanOB0)OnLg$#4#L_Iu2RGYXEZ198hcZavID2AkQ+3J3`l*%BVXBY_$CofIBq#L{wp zC*iPT#LVT0ny>Kp0z#Iwe@o#B>v!4KX~0$5uCYt4Gdrw=oVbRkhkQ9wz1vFYbet%A zRZ?R(#Vj7~lI&oIK4N&0dm$RM@I&0$p5WoQ@o}A@dHeVCJEOt8)kz3Rg_h)Xh)NxqfTk9Q}lf}22sA+aVO=Q6z4>&vaeZ%jMAop?*0igJMnR- z>Axur%{3(z>t}$9>s%;QBREn~)R{QI%%Chzs*PAI&mhVFSE}$;|!4~Hc0v##dS5GkPU4_&dOxi;*#V3Zlq|A|1w_P)yN^X zK|zwYt~d`SpPI;#IA0}9Dw_fdB|1JBZspTD_B^K_G&aL*{dJx89V{~2G?6GcV@FHd zsWn3)shY~V)D#X*t1r_^RIM>@qBKd1N5+{0o3LBztvYPpj$-U{m4b!GPno2rGM-|o znj!Le*{Qxut|Ng(>O1^B$G64PhUvvDURW#t{x+%Njz|IK8}g14`}9}1d}Wm%FdxN( z%&#fiW|go&C6vElm2b1t#82_tT|8l&wQ~L2;)g_ymCVrZ{_y?eq>e|z5BN8k1$96F zNMU7~Yu4i4Gi31b$ts7>FaQF9bu9g)eCFsRCg3Asf4y{nIG;qT;>qx4W#WS}hO<&> zl>7l{rqZ4M&0ID@E4Oc*xBAuJ4@XS;pZ2SH#5^b~6Ne=RZ{+X5!y5CT>Unt0Je2tl z+c1Z!HmClRm{U_$mug}ZN0%w0SiItW4Uk>^qLug@PRpE8`TH1q0wuXlkx0%e8Zb?HsNd(CrujQi>}%E!;x5i2EFC^Jh7hkt zXk7y?>vPi=3J=P7$dnQNJsgJanKG)-_4sA5sbWZ8EOm~G81ZQ(Oriu7ri2RQml36J zGoxV}teYuwjI?Ri)DQXFVmmtptwrGnX$QyuPF;(|W%T5zH5IGQ-$z4Hu_~@?9c{O{ zW#q>m_3CIo7{5|KjOz98Z~$IW#43RYjmHGyt3StoM2ctie^GeuS``F9cS-7+493?LOITIFx?{45f-)&C8vViLV_m00?YF0D1nZkeaXy-fZ!be#p!*yl`Xe6mZ~mu6Zg)i8-1 z!M2r5otO$=Vr8v5UWi&H%P8RWh}FMU3DSKo^V?&M+s_+W!90C!%$9A`UhorrY&Gns zkEcJxJ(YI=$9H8%TiKO^g;W$xbcOGwt3rqvr&|6-4dY&?`UbwDViqew=Cyw2b~S-7 zXu5ASWt#KGol-1>1#F6QWYm)dr&9!InhfRwF!m<}L-&HfF3PmgjBz{Nqd5Jc!*5t- zmCNGHUvP^R6TC9L->CZc>c^!34h7+t?X3J>i#zk1@Fn`pEx#nk(l-`mH$jJpn6d4y zyX0MR&dsze>9-7o@FyJ(1~i0o!LTGV_^SkEH3NGWb7!ufn@6>;xL_EuERD%E)7TCm z+M2OI(&RR)E~^6JQnPNEJD3K?QX@*ufnD7#jioMz+!$}`WrFsK zQIs3PL~8|67)+V%%ng^4h}k?gw5FWz?l(XdO%iRTJJ=b5Nz=*`I=u#&Mjr_VMKWaZ zNjDH0Gq4_SB-1lsNv0nRyVd+S`dnbnvcxJUP&brN%p{h+8R82C=GCNdjaA|M;!)tD zUVMeui^4n2&;tp&AyRKN4|*Z)lHn!f446y6r`$U^pTTsXhF|J8dtkg*X;07kJ>?4n zQf`&jIc@A(D~?zDo8ztK=kT_H<;PM@Ws-rkyDGVv-B)(SN>~hM71dSRf6gc)?6w0~ zGuGPH!d-SnhuyXpzJo62YJ?I#oN=_B&i@~YfktfNuyQNeLQyJXaBI!(m7&ICm(aBw z-%7-%js21T99*r_?5`tis#EfRNNw8A7l?h|W{og}uSX*zKNqVYtjiv|tqb+fo*5Uj z{yNOAc*QILUqs#GetEE2??^YrMiRUbi9^%wE~sQR)ck7WQNEX?TdZ8^l0n}{7Y(a( zzJ~!)h1Vg}uCtPJkB~@!F%%^ZdYaAB)tC$%L>L=bS8Cjs|I!1Cj|e~fuC?%4_X$Fx z!jsGD1~!#)_NCpnFBcHK9-Dn2iJWQ8xk5)KIDd<1cSVl<_&3JRsoJpOx*?tC8+q5B zePD~p79{{C57t-o%{6);^fKY8?LBXh{EImulH(?M1E(Myg$irpO-pOjSB5yHSAx+4 z25oS}d?frRw-QbtR*kCER}!-FhP0E0^rkj%etZx7cw6{ULrprsJ^f_3o?GFE%~k(0iWIFm)t<=s$@K^*scXRs1vxi?LD()Gp4Kf&Z@#VN|T925z z6leXVa{qa)iZak6-kI)GKJrDNn%u1j5(0hv**h0ceIm&|#A6%^>Z}YR3PlGt}j$`OU~NP+$;2jLNK$R6ec5 z1UmQ1b9=N1_E2Bt&_|U}a+>OX(zGjH{jUGkhX;y=-5OWk&QfctH~DWD=s{|TjltUq zB*d$?_;06W-aZk$tub!{6-IsL?ft>q2b7Sh_tDJTnY=YZfiOX888liY!LFa_LTR?S zkea2`l4O#;qteyM5Jqf)>l(oXsl>Y*aR!!{3rqA|%<#48?{WXO(b;mJr+b}rRO@W> zTyy=zYc=_<-GX|>JV%U}|I<~g_Vn5gwwQT1eWj8&Y%kehf1H={#FY5Op!n5K}B|-!I)L9wu}A?<(AUn?sAXfLGyMYgY4aA{G`=x*G}JJ zK*5ofiR)GV?wZ~01kC+8_uS!5Gn>1_7P=nsyFQq%S9(Ku@TJPph2Algr>MA<^1;wl z#K4dav+fE8}UouGSD>B5_wh;fpybtsvjIicuU|M~dx9D(=kO-jC zgxe}cYZNnO^Rclm|7zBh&CED6*)`)Pe_1=Dymx4K#Mwy<-o)m;&hv2!QiBt1K)mR@ zI;ZHoXm#84cd%?8X@}N%Y3N{9_8?o_DH^J5JL2j5qm=E>K=FCip`sym$AWR%AOhiChnd=c;r7JZ!;#8Xb zyWXWd+Nqn(c(R>?!>bZqg)_$cV+mTHmC30Q_0jsQ?!wI2{+AislCuaKTj~8sFfy)S zzJ%?)%U;hQ;V(d)15FulwVe>eKygE2sh~GFX!}yr_Ty;#YCrtc&{H9Wm{86f8dkCYm!2ubS)n;;`i>1u+rBtcM^)%ycuC2anFs```&lpXP z@k?Hx@hCNhmF^_tA8IejEQzu_O%mGx@z?Cs)WUcL=iA7Dpf%B2d&%!dzHw{G??)1+ zVyTsE*W_qkZx}!fBMYHvtlFjW@O+{tAM4GLOXJsUo}_5#7kr5g3aH>}nM}*9e);kk z$HIn(NV#-WnAmh%W^OBWTWM}vdZnN!3npq5~&n7L0yW9QTSZGUn-P2Tqbp zgB45N2(9muN5ckQ#GI4kp|@hr^{g4k7h`KF%w|jJ^Y42*l;fWMh3w&Heakwy@tVjO z+j%c?nenAfY^#i?F2(>k5*Je)H|;BM-=AJ5Z~&0>%>=slLXD@`>7x1C)5w-5?ZhKz zVr#zJSteGElxRD*VS`^@$*x$O6*Kl6Zf2QcQs>$gEMRxXI9Lu>)+N4()OAXX^=Z6f zqg~x$-6cz0y!z$14(Y>oXFrk2WLv`t8;glfRTsxYydOu6)n+j4^sr`Cfv(tG-G}M4 zjWGrpPZmI2q!@lajyd?VtiG@vM$SK4A8H)&_4MsI`F6U*cucByafPuThcS>A0B-eE zZE^EVKlK*nk15nBhNy!qb?h-C(Edw!+nBYzjENNDfA8KGe98w((+4!Aj*%D?*Kr{D zjp&04r0!ArhW5H2^n9Z<(}xg{ITm^J`*op@v0^e_mulPth}-G&pMHxWKkt<6A@2rs zo*y@B^ri9Cx$)GMg>@AlXnz)_G=uvsf=D;+@9#$ZU<5cT#D{g;<^m&qFLN)Xg+@W~ ze_4AI_$aIE|385MsiG51&q8xY zM492TRUS%KRm1t8kj?GhQu=B1MsuCFdU3{#?{b?_nIIHDAwHJ# zLKRAA=Zp}}?4^M}zPOZ&fF2{nY!xL$1iIClUm20Nj|&-bYU)yk@<&!X~~S0JsvvODuYKa#kqOef9e#$$6pGTT0U9@4eNnXl0l&rpw=D z!$m(+pV5FfNFZhvyca=aajt#Qcvy07h2Qm~|7{ z@}em{`xX^VDMhlXr7!97%`bKLW<_xofYA4%kaYQNnT(2BG? zshPy?B%bg3aQNAz=A6$NazfKNMh`-7)9Hz^6%gQwbgd%FC}gB9^@nm6pb({%c0`PF zM0l-hD^hzfC9^AfNTwu!j@U57X$$GV65OFjgf+Hjc@k3(jx zpJ*SSz0A}iHj3I1jasOCR)AXG<71&BuG74bt>X=BR6cJSBVA~pmSNF*+A1z2w# zURtaJ8{dv5&cIa@-%q0w>SMEpTH&YsXc@M@`vCA4CB~K}N2NK9!pv_PAB|tYoODC* z6cMDot{>+Y-1rW6<6~%>-Ov;lNJCpzRd{Gu9=J38h?-6v2*|z1*j9D^8ViZUobh+|)KOlWpX=)T7 z8PXNy9NhDH=L_klQ{PgH5jr5Ee-QRQS z*xIw-UzDOw_W97(UVAHFY5%e`XaRhsH^Cn=_;N7LoEJ1X4EiljeNC`m?C5uiKF_q{ z=y$Y_M>$FdcpT^BF;!(f)jSuAlK)ZAZ{b&PFm&k-mM_Qe-Cgm!-ng4zZ~9I87l$M^ zIFjg<;>y_^UMPR0oWp@WUG_>(D9!RqqYr%6ljR%uR%hjrY?{w(8TMEQRM0dyF|xN& zKyN6ZX%8R!YG2>%^#}U~P!IWgGjyE;-vEvydv~r=Kis5VZEjln2{#}{tj<1@jc~Xz5`da+@RZ9mr;f2GQNYL zVv9W9qR9(-$C8u6iG7*ieX;xatIH>>t{re9O0=%wbjGXAuO;e;t{7VMebUA^Nx_TpptPDi|{wMsf)xA>eEf~i{(_9EBENb zd|oTvTh21lW54`mq&W)kBa#V?-(V{6dw!yNUKgedMUDqMJhNE%1;$wc9InKC!J;gaD8a&S z`}3#h3N4u5XGTD5#?Z31!b#F=ymViaM7#J`>f}Z1<_V!CGb_8$P$NdsHtw%GP0JGk zcNU(-8uXmfm64=ms?T5VR1o8Y~?)!pt~f;hxc|lcPpnj=K}=i6^$)? z)3WO~ecd;cmD5At+)kr3g0tp|Z!hGV7&(ZpiPL&7my8x4-fzXGTL6md-1RT%cpSN9 zmb>DOY}TbgR$rI(W;QDtWL3DVe`m9f4zl{WthcgR`;bLYv#?Kfe?iVm@a81XpFo46 z43j=DM=`kk1E6G?Q$A}$^862foa8zC$=wjh0hx>(0x2QUMi_P-`Z_@I2fu(Gti<9* zuMqe(HJ>_+slq%|uU!C-@^k35U;fNjyA=(4PFah@?;%_PQSx6TUEY@gv=6UDdK$0j zz`o}Ke*5nE9CLi{1u7B2R>(^ldfb&y#z4d;}EXZ_n z^EV22@-5QU$&Sb&(-5Wg=Mr^80t-F8%vj zZvO`Crhk>Le|r|{SScM#En#AKd(#zhy1rj`&jJ3kmZw{H*?$c1_aBu-&BbdVQn%~y zeqqM|C4*`Se^z>R)-mlXp2}HN-EI|(}Hyo-drjig0e{5-?AX`(G7SY^v zO5hp6N`g{G8C6DBJr8oI3P(?3KyIO_Hp2RG$TZoFV949VZ_b6K&SfPFCc%Ro3o{%MT0>PX)s^w3q2h5`;UA}_Zm)JLPTFJnz z1Jmj9xohQxwdECeH79;N>GEls#6>mYw0G&}m6-L?<;Oh7&te3WbopV=OV_{6`xv2W zbm9nYcj9k*b<3qVz}o_+3Bc`r$~%fqDO*^z!!jsrBE4qmq`4{>T;#>7j)_6uKFt?h zHFdIlZ$IHh#(0k><_;O(<;%@|Zg-J^O>Pq}8H=4hHMrL9tx2Y)SRawIn=hI*d**@^lz5G1Hek zhIpJX<-?SO`~aUQvLnYk?jSy;ZZIPofK>=bl1&s>2|Em{s`YY?a*C)!FB75BhUSZ= z&(xWh2rp;PoH%KkC^y=R@?2jw)?5zCDDVC3PJ!q;9NDm-m~_)%z&V1xM!A}fmNYaMS_PwtS>fnWoax2mDP10a zhEP>d&jBjgx()#rxy;>vrw?0}w$L${x`b`HS;2G1SB5p`aLH272Y-Wn)Yq~nZQ`eQ z6B9u*Xz#v-rrj>2f0wWmm|u4u*7O9iiO zSTQ0MljBX5jhbpyKYdcu#nYzc)hi=A^`2Mc-GfrO5II!%^}2qlR?fYvZ&W#ZX7goJ zW@LLQt3D_jJ7dzEIn;W7kvD2LwLZ27YBl^4{m};%PQSQm((I{xiUXUCeJQUOPrGR9 zM7@qNP#{P#zv0t`dV4wb3JTfYhJ(4l#F#phs?V8i)kyZ>ZA_8(>)q6KuwU1*nKJ}Y zLz8th+0dISb-8I45KNjrby#dG(9oLp($_Wj@FvxXQREP27s8YZMK!MmZt&h= zk5&j(D@%PiB&I~@@L=u_XxgV`*gH$DpK|A@X{`%7+Dn_gy_iSh*i|_l&2&)oU#Nr8 z|JuP3yXoM;bWoidYJQZ-(fJCQy#Zy*uoGN=SF68?I%#BT@QgH-3|kR+9w(KHnra0b zlxhuVcJN2(s@dsb6}|tk4g6+(Bm+>Q#?(7!y1H(#;!I zO0XV!FE3`vyA|m#Bt7fEt=_UfO6i~1bH@q4tmogSoYPP}j;nDWra&>6Cf^)$?L}PNCRTz2!1Kd3$XFF}cgx{=6gd%a!m0`#D|v}AVm-Y6Px`-O$*wI zJuthUN~+-_&4EygPTV9b_}%08Ju>$jlNWR!5bC3Govqc=;2VY zq30Uq0o7sBLoQBT%d6--*mntn+H@uNI#UIyPM=X8BFxb*rr*@IEd6H9xu(B~x{8p9=A_#1-*3^p@_M}gdN;2v*Y3+umNKCBR*lQl5SV10GSc7O zXZ4ZaposUFXpfcBbP?d?i_u@6EqEGB1T3?q!;@8#+6a^>0o`~ZkjsmFT(B>gJW@SpHL8f#Z!^_ z=}0nGsI*>o6u~@7$;r%Lb}N~I!@f%ovXUFK8d>j&?l}X0bf3I|`%?Q`eN4YWqzqpP zlQZ7OkpfbC(x~?&%}amqxI`1?*CuoaxZVc!y)Ym5UOl@Lh6%KSH&KdAC2B2 z`TRAPIPJP>QzRFjz`|$uX!LAQNZ26`33&aIh$7fX>|c3!`3tetcS{Ara6y zm>)iwyhl3I!WKZ&)#VKBdwcpr%joLDwu7tRsi9rVfv)B| zc6A~8esPb&9bdd7A19$j4+y%<=i1<^aAvgDiELl9o!nQQJT14AD>y}+57v%INJp>> zb?l~C;jIK{md&zEA z*#j~eIjd}m67yHtEq@IXb5_}xN%ZFx1Wo8-lPA&Vh8Bf@zQ0a&jI^0YW*{%_9a=JC z&(M;w`x5f9G;w;VUxN6TgV#4r(0%Zq$%4er>xRK?t59>n%Fm>Q3a8)gIMrvx>83-q z^1@z+R(f?02&+RY4T0YNpK0Qe*M90O%{KegtuJ(>1>ifnh{pR?dN2RSXagL28-J%- zaL`Iz!>0K>=p)_yqt4~h;oiaiO7k>`x8Q7gwpz`LB}P=o64zIaR-B7R&{9N~-*dnH zJCdlHvHsh2NaNPKwL?qxV1&d(*EEMi1E@YDfzu)l@S1@TPDfTNKq}F~iq?bn z0Znb0d$ph8CLln(cweO2!Q@;gvz+)e z+%FH}iw8bFb=7Uf$fZM`Z{A$PXsc_6)K)fy+6GXM;=VPl+`z6jU0wp4SmxbqmE#}h zr@jxfAm#v;kRat*v>i2&h0R6{ZErU;M&)^1cW|P!F3NGe$Y_qZwqbVWl##As=HDSP z;%fE(G&*Em=+@rsJ)^7tkE5WX+b!Nt`O?wwnrQL5dT6-OpI^kh0`^07{1yj*NQk(s z;|(REOw=*GYz6-$kpOuRSBGiiPU7Yxp*daDB1J7fQ|e!4}sKQIbd_G*VO!8>(OZ4pBCM-)<-|{Khot?QyN>qg!$~!Dj%s}`niiN zGGmPzkDE?&I_nR&A=6nc%RomUJ+KBf({m_;Jf#d-5zw(~E zmqJpnIJx6zA4=}HHmnlI6!ZyfI;3UT(w}!j`*Us=?N1}1K!lQiob)|+yhOcu`kpbS zxJmBy9p$0F3H-y#&QEX3_CKpTy3gb>t2Z^7ZycT4I}TzQc}yO`fsQEAtMhI=!E*h1 zOu}i5Cg8aK{+^SzcVq$qCl#12c`5RMMn(KJr03Ra7k?qPVLwvWMZU<|;Sac*fW zNz|-maB`VG@gjUO)V75mEyM0}AN-jYe9MworsH!!DGSagLHQ$hm zuqtpieJsLW)yF#+-OL`Fx*-&QsX6~im4N$HAuid@I&7B2{dWknY9dg`IAi`(TZGSA z-AQ#+i48oo=*Mci6>ozJY~Qv_>&6E)q1C0SD8CkeU}JVP5L#41Qfdf)vhYrXxC42~ z3%U5c5~t^O;qyvD#PztJJiQ;3KC{&P%M6d+y(FjJhXp%4rk_u}A%5)q0G~G^j3z>z z%e~9hX^8t#cAxm4;s3Au%)#S=%}eQfj$*H^@;(x>y1llMOiRVst&i_W4CZFYwqKJ_ zudry(#s5*$GG9uwSNO!1jkmoIIC)l|m|r1s-$R!ohZemi=zymQ!rA!BS&~F6j#i@E zN><2nHo?2fCJM$xp>1lhkYc)m30j|L`MQEDN!M0VWt!b@3#16ebqU{k_2=rf!nX!i z@mZ!f=<~j?=LVUBz|k7yg0+H04&M*v1EZY9D8CNQ7^U~G_ksFeyaf5)E0Z-Ct(;yp zcAb2#tz!;w?blZ#ry-slj;V>jyqLdWvi@m^nq|vX=n1pHr^~xhr13ll-L_Bp%5|ab zU4C-(H{v+BVCtQ5sUgJ)cG+Lem+1Y8K_;tv3Fd_Xm2>Ib7?h5seX(hDB3feJEes%~1N-Q05aDdCYYg zcu?)+*%wcrbP-^fZ+)BT#$gX7A@~k*3Dedz*sZ%WKfB8>5naWzyBfw6?sE_1*k-mSS!3SyXo6&-i|aj z@yy{Vei!8&pZdce@e>OJn{@f_?t`K~*~?Ku7)&xz}Ge&%@YXR_aHc-jA}Hg0XQ zQZMp5CyU6JN?Dupq2B_RGpw>4^^Vw#wUrhmdqkKRZR`DhC^(z-)q>S2HXFUQDB9=h zY&6#~5WX%{^_>5kIvxcEBg!;nL35mfACCXbFJXbNudCdoG_GlIpDB?Dbky8b`4*3| zCh8z*8{PPfLeILoNwM#!>-ivsaCPd^cy6DvG^>9a2i-|~lTV*fL91UQbEBMCxtZT( zlY9HsKOIyy?gf>3T;I|L_c;&-n7D$vb)t+rx)hO6KYQ|rb}g*;cuv#v7OVV3Ij8H& zGV6*ibaLHNG{a~3-AMX+em_kSt{T%t72}1lXCRbqg(gwAaGy#i9dfc)yUcj=^aC6*v;ka>UXzZ|;KQ1nE6}_nNbVM|K zoD_<_tM5Lc3Xjlyyn6x!0T3JYvYB+k`8B@FZ{3MzNZ6!I`ginqFbNR@@qI!Ckgvo@d5*Z1{I`{z>O;9 z2F5sa7YLBhZFqLa2bazPKmUVQRqPA?G(s_B2fd5I&DHcMp&I=!2|Kvo`#||Udbdrr zdnfy_L-m^#v)7Ojvb_!Cj>``T7GD z|I`&%1WCe3(tAH)lM>zy^>v@E4ej@s!vAq$7Hw<(F?M}F?lIn9&=0_-&-#hLz}sE+EI3WfPf9(tjCR-&B3;^@khP#=C6M>^Kdo@mYrO zmk(jj1lAAj_aeze$aL*0s^++JDmMcz1%nt5Mrqz6M{4GctA!arsiD4H-(tZ$pHd?V z?q~ry6{(_8*ZQ49^(5+Q#J;OS`)v=6+Y#z>Lp5w}X3xZkvi6Y?)C*1(!39`SL!go9 z7>UJ6ZU7@Iq?AChk-PtD-8_XzT64RaT{#w0*S0mZh{L$HhH)kuSwOdRN;c!NsWT>7BxbAyrug(XQcEe}uVja49Sv*Mr39vR z+x}K?&Aeo_{DTMUH|@QaQ zhZK(`uAs~oZ_klMMKa2s&t^VxT;lAKl~vS;ezSKRJ^BTdO!Tcp9f}BxeQuq1+7oQ5 zxND92ZDZ-`|JC1>ihV~PdndU1DYL~({KFZ9FJ;3OO$;wxUTh7mWQ&Cp24(}A)KJ5r zCBysp&E8yFc_eb|QN`YC8#C>5eTmmpCn5=6%D#M@cpbpYNW^jl&BLZCv$MHp=F6bLa zI_S%axH+hsznKjb5|?WNO35@3K1>blzV z28I!_P+!3^@qPOp7CcST%^|YwDJ0$Bp=~NiXGX}sVnV1rqq9b$KNZ5+1kwXSrlBr8 z=pnUnw<+ga7(l@(BcBuRB)=uwHN0=Z`fS(GWym-){&o@f`{iO*e* zY_;4FvG^ig)u%(X&YAiYYmJ@PLWAr00T|?1Xp{6vta%ezrle`A5|Bn{N(SV77o;lk zT9&)ug|*;S`wQMpR)XsyeFr=v0ZO3wwFU2pcN~KVXFQFZzIWqGYUWzI{PS4v=O_W} ztz)?66y9GMz5ltECB46KjNiELOz(fJHw0z7v6YVD;k@sD`i`>nomLWWru)v;xnsQr zkGYi(vjvQ#OMAbDF}Uvx94EN%Z2djfn+gl1Jv{%N_ghXscQR$%ceB)9`b8U`Sul?E z`oMztF2}nqGrs*+2JMCQT?gO6Zt^CJ!_T2A@M}?CfS;E(XQqD_tP~R#vON+;W8%(g z=40i1|K~a@tc2RuFhfP|4l{N1AoU+k}G2X)1}TG`Es06*j@V_l=NzCU<40!`q6i^Sahn&8uj9R#~p1D`zf! z{%;46{JC`7^MY>I$^UzQ@?&7{A{oFRx$Uez{w#hLC0_kAXdwxERAbLooH;|LYJg}r zAX9bYoJ9#?W#-!G0>+`yaY2ekml3nVURmzw?tQl~ReqkbMQM)=Yt0$kA&f0`2-^QF zKB6dGQQoPe<_+SV)DBVJu|XCn43|ft^40x2tJrP!IR5+Eb@mzF+q;T288@GSTXQY3IY!x>pO|C0 z2#tlPffIu`k}J|(e0}}4P0^>{Eqk5S4 z!pI>`#^<}Xy?7U{ZF+TQ+gJb8_iY;ufCzf9JLk4Kq96R=Ry@;+zqxBzrJF={dkRlya*I3GwGR7(Kiu-j7SArd5{Iv|Lxo>UgTmQ_9Yfp6|v+gLz7|>bIPVs3AQx;bj_ zXk`msjV5oYT+GvS-M3g0q3va`#0Qt)$7Jar=W$i4$a@-CYeRb%vDsR8TneJ_k9erE zg}hLIZmi&`?C+4R_w{-2oATa2$a|OBIrn>=sLg#3=e<|wy+`ui8}i;Km@a>@+UlG<``Ww9| z$_c%Gl0Pql@arYi6hvRI;CK9S0tUs@BUCS;*E^DR!oMBAcQ$xLTxLV{$Wl{9zMJ8AOM35&T;ca}gZ8Z=y0GEO-wP9K09#01w^+ z1PAZoy!-&cL4G7JKR|GhKOrwaK(OWe2yXrS($I}M4;akX_{%tx<>2?DiTyu_zfRcw zcZ*jg=Vj0vTJq@7Rg5ok^Uyv7R=2q7ecb$;`=~!BZ>px=)-}~PAE31=T(h(uI6)LQvRQNXjSb9Nb&*FM0q=&i7N8>ZeCCyAg0k`7`}=BrE@zkduXnSi0k} zu_4>IT^%Eo=DT2FU+zzd)IFq5UR?Z2ayV4><9@5i40y=RHN~kVwiXu^zsJN4EqP!- zm;4dMsihX6qbOO|jSKx!hX(uDWKB2jK~42Bt6MFXz}IzeUDqvDoXzjvy1HBHLmD^z z3YT`J-mh!di`=}&$smKL4IqO4%b^>9rE!Z@S2^*;p1 zu7yI}dOk3{b+v+ppBw}W-@4ils(f-bR`?q@7gbUd0k}`LSmFEU$ODZQFqYJX`^Y0<`EIB?}{Cw&#q}yGKxft3MBZl^IDmIT|LB~_~ z*|2i^n~RKvR|CvKRmO%9JG*`Mdt8G#h`6lJry7Jz=8&UXN6^m6F-JTwd4cVK3vsRU zv@HNJBQ*S2T*rrKs#p^N2E{x<4)Uw=mm$*uvjCxEc2&8RXuN&T3Y7YVtk}B%4EI>S z+o@V$;Pw7HQ){8r5$r+Rd8Yp8zg)u;|Gn1DgE`#LRmoqK=y_7Frirp0ut7_lUdCaE zwhc|g5@So^oZ4qc>OJ(S0L(A^ufowJ(lFl3Y`wzfM#zr}YV8B3x%r&RZ+J_dRB1+* z5geNjW#>3}&EmuvB|}axX}&#ZYeX4#iM9>R_jNwyH9WC{c(?AviFxgz4lni2v$}9v zLlat=0EVglXV^>gNXQWy=lGqUJqPJ!-Us@;UHCA0!CPHWCzsT8SU@7FEb^ z$d%8=t4y6nu^fGX@B7xe86Uo!Ei<(xoEN_<2epmfCfZ~x4RToDHp-b!Mdd#UXrdO<$88vBNgL)<*)VrGnE3HCg@>7F*96V&s>P7=FK%7qJ)(T((z2J` z^G`f;@Q2@G{PUH+zo%qID8Y|_sg!@-_ z!dcaLMRuO(RAiM-MV?B-lNak0R)nf&g{pcy_%b7_+UqVJU5I6hx=;*wEyfuJ#U3}R zWdGikq4rgl7*6cSLH{-qjGkh=+|Lg++9iK~kbO&OAA%&b*g*Ss-)M;-RnL6 z>lNk@m% z{>#s_3{)<65Zn?tEw)Gh?a7-}2N%THefGaHzNBa{LAy4E?Zi0;LA;Mgs7th76K~he zyB0pmaU$d@xMH?qwG8R z9j#PHKVF;D(e@V#cXS2m9g#5pVY36RbskE+&!5b8G}LyPEmH6T1K_Qzkq>mjD63!5 z`XNZ${16`w;?o%)uJ*>r&S)137im*9o3A*GuL@u4!!uhtEG zMI4wr7+}K4O9&NP>JwKoi22aic%3FE7(PQS(Ptcs6M-lA2y9l;LWRW!$Q z>dENKskh5{64S~iM`9WsspABYV=sP+iz0!zkog@W!yH36h1V)Sd4itpBd0*5Be5xs zgll=o#xI6Y6;+VF*6S#oS8h>YW;^b(20BiKWIm%xOA7KlvDZmJeyk}McnCw+;-NiiZlD5w<0|b|| zy|X}b;T>-)HD~wxrge`Io^N3zW$B$=Z}N_}-b!Ju*FWwpb5-sey0KhUN_#@M^q=@u z-b>*a9c60jx;bP(rx|FPD+LHIPFGSL=t{K1=%NguRF(JI--SA|^7ibD{wA~&XFQMr z;qu6UGe|c3ju4v~1`W8-L|m#yad!ulNNKfpqH|9C$~(caAS<&ARSfuDH#Xb{JjXdRhx6l^M&qMcFSMnEu{uNe!LS-5*9=`k{y_A3hNty4mzn&W z4vU#MPLlEG;k=r*XX3APswj=_5Bi6l_ewWZq)vm7eSUK`&_69dj@r?NhhJZAY)mp0 z9$bo#ezt{BTlBGU#f{#te}S6hZC(~f69wz}6`!^YyHNEsUDvgaANqA%qB_3wU-~oO z>en@onqT~=EUFXTxjFYya?hO7Y$E<&U9r#K|ub}5K=zX5R&Gl z3lQ4ANjgrw6Q&6l{_Gn7@j(e@S89vH9T~-m#Gv@s3eG?}#NX#qH~@rnU8|ji>-fC*0$F ze#@{=E@O1fcL&%t&hb-;$Y1;n=5c#g-s2<@s@K@56_`IPxpks1sucjNmKIvEdbc3sa4)7xCieHlI7t^8nw#i=hKuN?YL z9jj(?@-MOXK+aerUcBX}Ociv|EGHYiH}x`tLV9^lFC2$R*Nm-4`{Ir$a;*Tn{f`l; z=0!~i1n6DlswuI=Y@`Actk!_-XyQa-9++(9o&6WFtT`rFon&&=e)*?OYW!-K0$KB)q0Mc#2sslSVSQsv~6 z$!HJ*`DEnPwvX`6RcA8Sv`RjyiV;mJ+%$6U7y)1_<>6(&!RTHt*+3j6P>8kyse6DrdD9Px`d+V>F5R)2m zaKB&Z#z#>wFSm%3$w5cXb>!5Gj_*QFTt(lG($00}Bhk|)9HerJVJWK$wcSXW<>HLc z(v^OF4^yx(EuH9_>L9K>+hAc@+Oe`QE&Tx86r!d1N?)q(=zobmq9-S0>59ZVA*PmL zcU)pLHIKjfd_1)1Zn%ax?f377g(P{gb=5@vjb75+vtkI7iAGRc%Ecp1=e7*1a}^Su zlL)n-l}n&(2}B-A&dI+7>P_pzKJF7j!=d`NOQ25EB~YQZ!GI_hpHMoQGwr*>I8E<@ z@sDh_OE1j7%xvct>gDLe0tV8v*Re6X!dU+S$oOA4$UA>4jP;+101I%Cn=BpKIL1|c(Rf`^>NztB)~Xuq za?o{Ub~%G=m(zR+hP&p`tuoaet-^s#E$r-)ZlCmBUi|fb7t0M7yq*%6x=z5{)cG6T z)0(vccHWyc5_W!{IIu<+kWC5?V}^mBPV4$tM>{8*JnP0iGA+W_vWdnFwj_{DStHh4C0q$!=nj?&!#jGK5WBM!@BKy(( zC{}3W*cGm+%c=G&6x&J{s9cSD&)=c3(ZpXEq_gf!+ROYC&A zM6ch8j&o%R)6r}qh;1NC^o1{EWQkt(a`NcJRRv{x?n~GOU2hu}^0V z%e8}K+Ma)Dqmzn$i)oI&Bj7{?jye-bHf|7&54$3%PuFn+wH z=ZX|hN)}*0``_h>{t_XAWwxJQ4k>c-#CKGO@st0)pnY|D0-s^V&_$kzll0~CL>hOL zT?P|s{XgjYD~kX>%#|m;Q&66`@+((16gHTFnl}Ggd19yy5Ya(9rrik?G6ra!ulPIX#YyGa^rA8i>OfgYpjF2z_C8?gX{AbjjYf^_{Zb;qEi~IAbpZ$?i zE?CK*cKsPZsjf>&7kl_T6w-m8_q56>$Qy2ZdDE|$7KvdGqN9)zC5}Be+2f;nE>=p; z!4T42o5z12#5N)^DH4k}59VH3Lh`-Ej#t~udakg_*ON9Qc(x3?evGl3Mm7< z_E3e*gYDR`b5p4>WwC?;IGeVn4f4y3c-wxXNrg|5x$8=YT>gl5qZrmXD!E#PWnjevR3;K>rp>A%5%0E48*EqfNtjWqsry+r>`;jv zC_x=fD_2dL-Q-%KpsA)6KqdTb#}mSstarI+>$qP!#t_LweR5ii1C_2(n&tX6LNve_}9Ga4tV5}Yl60O#Gi zBP~Y(5N4=gCJq_N|@m~H--r%d<1$2 zqjE3Qhv$D0NHUM|3-3FhhL0I3S*2%vas*@y*1_|E2dWBoyNUS?&@vQOhk+yA#My>| zcjEpIjI<1U=q!WHlRa1kyWENU1<(TWQPu4eu|Oe&8Wf}%@lIfVIr_yK19~;od(kRr zV3FvusWUD+=F*wdX9bXm`ZsO_G#re+dXPVQ(N_>V%u&&EKhLG2Z7j{4HVjA!ia8H= z*YhMW%0WeNd6 zpW@hAA#%Fup2B@NRDHNSw+{!=g5g_;%xSrt^!7;jn7|7oAC+Tv)AC-l?0E9WKa-Rr z=h}9d@gzKkA>`4?>m`qS=c0&%fG5P_^D&k;VwmYW!T3^?QB&J;-xDA zUkgHGiTRC%h*t_3?;2*5!5qS+ZPD&X{MUWjox?=VquuQ3Vn7mry6Ia!sCT8^0~l$R z1FoT;Sq?aH(u|p1IN;2wa}+8J4)_fnf=Z0Rn_>QhXkrR%zN{W=^?5p-&LvOX+E-a>Whph(b}N;Op55Gk=_fnVoj3UQ-<+wV<;^H| zDMXd&#!3FH$)797kq*UX{D$&-u;%`351ccWYyEs$ufrXl?&Rvxj{#=VqjwFeFnMua z|Kv^NRpj>Xw7=wm$@jq;PCB#6Jc#o9b_9J(U4#%edGVJDsS-C*w``+a#{VX(GCLXp zb~5oWVQyl9sef#@`VxCvphCrOvhk z4K4JkXca7ZMl-}h523lEsFS4H8}&oQKFpX4S#{2}!j zJ;Pq}Kj2e$S^4XtxJ`A371 z!QZ=9-(L%Cy^FCF$g_B^>{@IAZnNi_)W7{_rA#p7U+zDD=RcSG`IlgDSAI|bInPgD z<3CIM@}vA`!hb&CKTq|)dz<&HKdphZ5698dH}qE&el$Ue&%CNnLWaERyC+ zYjWVmtosKd7x9&UO!ornZ+=iK2t|;yHNS#L2-8VtmfJ-cHp|KNC2a4|Upo#I&U5~F z=}xVb?|6MacELt;XvI8IvR3}w45O_;Yu6cT{&tUne8 z;V9ApVK$aXPDgkC((hNn~}I*hL=7>DU-LmRbg9^UkLSqW3wV)Qa)(@x?0iPD$2gN-{u3S2-c%D!uo+4Y zx{>8KI%HeZz=0cDPC1fT*mjCT?)mH=z3p@fIG_cDfI-28F~u*o-+Ka^8p8k1Xfq}=U>Xj&hm%gxNI!G8owQaFXI(KWE8?| zTgdpa+X<)b#V|j5#{ieMRn4cy635lYrw=|Y*?XrB=u2z;5y0ajzIv=}B*o&m77VZ8 z?EYGo-dt0>k5_^(M9sXpr=}9vSnUlBEwXu)9QvFv;r;3M9qCcYzW=8eMX)DaSGyOx zO&Y+nt1*{wHgdRZ7domCOAg07XKPb+H2$Gb{q1O?W^nzsPwJCHiB;Xpdboh1x+*YU ztduxt-P*i9nm8#2TKXuIS2TJ2I@RZmq-7ikB#6;xZ-RtMe|!F_QN(&gJy=B4Pfv$%JVCTy>>BL~R{6QK^f zb|v#v!bR(6p}7~aEdmjvAgT@Q>EnNAFX0_Q4dQV~BXwocwnyVB&KO|Iuc|TQK3a2y ztfILJ??`lhsk01htmieMB_)+Nb1RKz!z$pfGay_!J5e)_)}>bPYeqX26)iXYBicdS zD~!a*-XRm-Wyt(yfXs>KwJLvAWBphe0b?<|sJ(AMb*wSy_=H5TxSA*}G`z17 zI2TREqVY3%KN}{5*K8cYI)1f}Ej*-&KioY)7NV8&M{H*Thx*h&$>P(q07`0-VUCF^ z&)c!Zt)G?7Icn=;CCyFH!ykx6Q&f9%&#jMjYx+fe1NQaWvYp)WUc2AU&hNYRAtn~| zSS;SNCcggkM6780>G4?4?Xl#D(t6IN#`?Dxe^lT9qt-XKQ*mhFc{Ijv4z0#NY;7++ zBhhbHh%k^={w?ViiV*mGirB-u5E2kQ zye?dR2nj5XcA#JaTE0y09|e$azgusjiF1M3-q8-XFCo-p2g((J7vW)SDTgW7bcz2# z3|=jCQ?wivZq*F``Gbz7Pne7n`Ux@e50s?$&f& zd zla2S&+z?}w0G0Zc1Sq(XgfbXg#NPqGL3$t?SE)3)!SSOWKVqP$C-OsL{tbsC4IAk_ zbIRgzVX1ZP6ytmW+r`PvmyhUEP8W6FSD;f@@h2nxn@BY1>fXEQYERcyX79`%d$`~I zj9uk*`$C*y3w66E-L6UWlw2^9WiP-N@pHgajid0C?BCu14UkRGIkKqooW?WPf8Wjl zYVYERLo7bZIhe!=EcSCCjdJ9^k<8>0C?zqn#1Vj3EE?D&njAKa`wtWSZZiI&?MmOu zba{fAmy&xa0lGe}kf*rk7> z&z$~EW0C0EziSHhui5pl;6Co^JCXIzhYfl&)gxs5H)I^;`4Z1Oeo=F-9a#c_$J^`{ ztBM-->OhyBJ8jLNtNgq7gRY?qyN)un=pje(joiqWK71iYsqA>oq7)1|Z+{~jku5B9 zZ5HJsgx(&}gH z9mwz(p_3Zi4E7hhV5y_&3av}Q0-Q5dj9-4G^M`oT@9z9z_5a`J4?kaoaG&^pK7SZZ zjvVZZ99&?gtiYC;^# zKL~ZmO-Q`@QsT}&yzoi$zZ3=xE2R!3acpWoxlosBnbHY_)aD!AIT0l&UUlLW_S1W$ z4?pJ=VLDPkehA$4*|)G5EZDpqF(qd`c%afFA^u#td;$7@-8NA@@CiGf!6DKFH`~GB zS%}l+*PPm|NDKLi7#37)z={P4J|v|IOF4XX0Fq60G|^@i=BxuL1TrF|soh=U2oBt^ zLT>)C_iF_31OxM^w79+X(HqncM_lis|PyOf#i2!C@UDSHW66gs z#t{3T_S%vLs&1>i$54W78jC+L#>6>v}`wCE*%g_hiI-M;yD zzcSo?-AzUk(E;0L`FbyW_hu#mCXl6A!Lfyx&B_Tw)dM)_#`7C%kS=Pr6&CRCK4l@l%M zK0bHRqS`ESlmbS@hNMDme-tbe4%9)AsGOKl7`=%Z^>r1>j4_BARYTXK$w#dlG5_h^ zv_OfsTgW-KT=ebs%2iZieg81jc9GwAm*)Du-twc!^N5wVRGN_J_ua*gdel|Hl2+y| z0V>gipTde(CIyHKac6485h54zTc}hgOD)$yQs*BnSAnIni{UK$Zg2m)d+fVy?mG?N zaz6kins1Gx#Hyj4yZ}_|$shPi>S{~`8U0sckNDm?a3bPw>(17`v)uT0Xi48aZtlBx zYP)Yw2t7P>kIC_&dvkWR8}p-W6X~5Vv)@MsT{HlAF{M&feDCX@)IKsASJI*k7z@vp zP{iyACXbs3tvi^6XTm`vTh}oOb+}B2M)8!*&X_Xt=-UOP#koG=;d74ONgVj9ovG7E zS;bPXlvEq+X2mAfE{wOY0h6pwYmKh=tk^>@O zIeIeGM&u6&_#vM#L?jP6g5j!NX}5UICjvVG<>U}!0yh$fKSQ_{A8NFeC({f}!T!{p z3PxKQWe_`oN1TrarXyS1758dL5ci6AKk(I%Y~ZW6!Nl)0!X>?jSm3Ll+Hh;ts$)3P z1aVH?#h)nS!rDZvd^=KG+4d+ACt^Kv;!VJ!2kWzYXg_rUx&qT*#y=oTi9$|2)w&vv zq>xi~(0KLi_c&<07lEIiPMr={$>7~h)6`A$#P37=y?7b7*B<=^X#1k`hrWI_is}C8 zC}uswcKvm*qvsr^(&-D~*G zy^B*5ZPYE4=PuGGv;%!(drf?MYe(tniM~BgkN53XJLD+^s_K1Ww0K+R9gZPVm%o3qTcb-zTCtD?GhKcl z%QsWB#CE;NfatMqw~AS*ohThzG>LXtsnx#ii@BRM(4R}aiJx18zh1lgwDyLzQNxpH zBA(Lvb|92YV%w8sJ&X z{f%z>L7nj8AJ3-+(piY!ap??*^fuBNbGm%rI-+LP(V9?@>R^p^^sT-5iOdUPHFO@C z-9Kn6h33P?;+>-I19bW&9Rf>7xb<^5z>QS4bqx&h4Swj<{k=RLT*|htff?S$d;AqK z!$ZUjD|V(9s08vI!-cM2l=Z{$lQ~!a54_|>bwA)G|KLTvYj8YdXTU>VhKKB*GUoI(XnJ^(3o(ZJx%+Md__2)s5Nwq#hv1>6jZ(LRslNQ*+q(p&6W@Xra?f=6 z+nPMrRrY3{@hh~1#LflYgVz$DZTYhA@onqoFf+EFza<^d(nuJ#7Fvz39=Hk>4!@qRy@_o)oCcH_b-f5X zG6Kce2*lQr6dA#Cj7uM}>!MuystaynXqhjRh8`HJkZ!tBCHd8n8V{x$m&zfcwOY<;R@x&?YZ8#CK=m=iwlNXfg zTqpzI=$+1oHHi{}ICE%m$m^lDx9E3$V!G|kPO$lui8@hWe|E#wIs3EOy!iXG`E1WF zV0X5$sSidgSdMS^I)ftqX&D9crW664i6K!}6&>=QP}@D$IP|9D=@9g2BpO&keOys@ z>PQ$97IF|44LR76l64SL$FXHTn^z6?B9pm3p!<^6GIE zJ0ByWf47U%bFz<%NR{X~TdF5{_%c%jklAE;uWo* zr<<4JdyRO~OeT|5+o-U=jRJNZ^&tFf?3=<6rwSZ zFapsvubT=dQD0*gGv3cqCCRgiYfGE7Zb{^9BHNcYcU`w^;XuOnPH3aEws>VX3{=Z9 z_1aZ5;+8K?L@hRsF3>1%HF>G|hS&(7%a!tJjP9%(PyIj0Lt_9;RvsG3+gFo^66(c2 ze?T{3^B?j|ZRZbE#hCa<5cX#vB36~753S}Fa-3vz+d^q*$;a`I^D!b{klxfqpDpXU zl|SgiV<|6LTje1a{F{kUMe*H~|C7oO{V(NJd08}Z&@U;>T>x4LzQ!kZD_L(EDs#=K zQzi!lgl8=6%pA1H3qfYj? z^#D|}?86@9n`mOMJIRYCAFW&u3keaxdnw5Sx9Zp9DuAG@8u9Y;6tDh1()2Hi;zD1r zt1q!9c`x#AdEMuG-b+oIs6D=efJX!mP8_+hB+suK0s+h(kxtrGPX-GVw`@7}^$@(tmKrLsHoyNVkWOD7Z+?W5n6vDUPRS)Ib9xg>`DF@0ec#Bg8WOjc$d;# zSB@9O-95B$of+Qk#JY+NrI?;;Z=kC%nuv536;4t{aD>gArvb}H!?#Ierv{p}72OCV zOTaMt%Inc!^2>lrk#|)DSPTB%q?v*u;<|UB2E`JtucpDjGb zZ+Ey(_Bd^SSFMpo4(h#y)&#Two#bdMi$qEb){1VsUVGy2`;$GUQllZ}m-cCjTlB4LEp)5M+RCo;^&lPDWg7dx)&?Pr{gJaPouxR+SFX4e7@hDg2rq6rkzqn zwEHNJqRgntDisfT&)eiHKfylQtC5F#hV@M*pv0*j9e+e{OwLvfGz_PUM3c8W?O^91Xic@l+@1&#X}Yw#pXz?fu*khPXoT`}aVlL}J8+7=MztnA#Nd+(}7^GD=4d zxux8xt4P2if{uB=WMNDxB!REDNz4tmK3U?;XHcmHru%XHS`6(Tz$s~2@JoG- zmEC(Dn$>!J5Z2h#Ed4o$KT3H*_D`PE{rB7Kxh~axu}iP;pZECrll|xWJZ-&k{ z+)TSUYs0k4c`OW0zKf$j#~ovf_kcM;zizvF#WDP`-dn+XQ4`BCK*tAn6ZL39POz;v z+Zv!z>NMo0nwW-G;vqra04cnauNoW7zPw2Cny9r;x8R+4$(U}nH5S_J4ze}Zh;US2 z5E78)V1MTWG(u#OQ!RcRky)1asmud51mL|(W)he!$)gL(i!o!#)zT@vwYRB>S zQ`KaSzuvh3HkLfUnmB7+9e+2(lBezB_*-2s)kDepXo8TKY%7;qkMNg9Grl&Bw%IQH{z47&_R7dnu==C((iDZ~12b~{ zm0oHa-^2&3k2=@iT>WY+VfADyX?nyu#rM?ddrhYfW7q4;Yl&;AeTefHW@h;Q!t3kf z8&W0O#D$JDva*&jDaaAX=csUo>$FnZ3p*BUDOnt*P+V~m2 zKTNuHv=AA7*c|nmp-bB@3JXZerp*~0e=#*(ZFVjFb#L25yp(?d(!1s~HF?88WM>aT zl{lB#_bKQzm@VGZM*CeFe#*PWr!V@o@npC(I! zFMl|H%z5yxFZIVfN88ebgejP(Kl?f&Y^PBF?IATkWQkKI-k2qcQN82Q-k~L<%5FyS z;=D3DoKt4|sr086j}`{w+Ryj5Eaad0B5|3;mb8xLm$y)e@#kd1k$Z)$v<->SD;hA`AD?O$N9HO%t4XRx}$gKrgs?{x8@|v4`6a9 zdN1VH8T!?C;nL6OduY+0C?J-P`+gnH_FFG6vML(Rtc|~sE*Y+_d*M zOYE;}3SdBfSMDQmMfc!+c`@1P^4mz{k_&hBU%RvtP;ai=-{;n?wGCa|D>L{WxTp2?Cc9g-2yzOJpNB`2xX3r0cy($8c2eLK66Ghnm7B82ob@x1%w+3lcCfi{l!Hh-R)M_J-n(o%Y7!^-6!J^vj*&Cw7)Q>d7G{Z7&YMvfl4Tboe6C7`bNf>}WMuEqqR&x5rtTMY z4W*2z>s)y^@SMT3tG@G7$$`KohsXxYVKmfc`-*3L#O!&Lrf!e52m0Kgbw>|*{FYza zBJ|w9qBxlMY2<(&KgksP+!Z^=iru{;%R?xJ=^(S{1^j7k)JLgYfbU?w_;@itU}Tr5{yXlDJV2Rrv^)a`UR z9>DXF{&>*W5n~H3iDqRQdLiG5y1e2Bqxc-w`ZT`w6dFk(`yeR!{`Ib8hUuoC?%L1L zqUC}Uea(Stgrw}lK!lp( z(L>ZB%=Ccw!7B=bH+tdj!F!$Z0BWMLK@1I-UEcc2NIi&v;k2W6D&SmqHIuwR!6#hcDAIrJWir+~A66A#fWoF*p7 zo78!2;*}>|iCZ}2mN=M%8V;GAl-S9ZWN0PcEzyiVC4botsx15+D+G?zL)hvZ5e|*jWA7bY< z(P#>-1UkFk!pvcWG;8m9bgX!1P3sqznz~WR{YW;oqh1cNm$u3lYRG-p#La!bC-42i zy!Tak@9Xp4H|4#5koR5$u5#Ne%Y8@NdjRjD8wY}t#kKJ_YvY@8Zy?nTv$gSe5DnZN zvkhGTSX(lI3ue#hJE8XGzPsEm`)F{x>?W$Mlg5T4Uq+fWOQjXl?q(WCf|7eR9^$Sb z-NqH9RYl%CI>-8svxKd)q_;ZyS|}N*4F-xXh7% zzK7y5=V^jz3I=KyHzD>@I{S+0;=Tnws^a=Tfdr!hH^{@NSYlml zqNqVZ6EKp61a2filvhwxR6r~!R1%?9abb5c+hsMak5cP{wxYJRN-ZEFkMK&sCt5{( z#~0jd6i}3xNdDjNnYp`nH=BU{|9<~}KA-Hp^SEcuoH=vm%*>fH;AZSWbqU$rg}4V6 z6<~lY2(T{RkcBAdV%9==v2yeZa^tANJ}I_1Eh>=|z76v%ELcl?*2HXnjE zpcLekArV+N;ND@d1DHa7s&O0W2|!EcoBICG-^ogFG4B8L%6*@6oiOkBR6ZpFQt24I zO2fUH(JL!~Uazs;RzPXzUzA=a!+Rx6RaRypk`~3?6}`S0ot$37kQemA;wCHiGIH@b z5*jO6g)C$Qc5>Sm@~a#-uydX63((%)gi4YRvApB*NBnc~kXJQ={)qdDF=07Aj(Wly zgg{c4c5(M?Hm*!?KD^xJe0Xt57crGJJ|-BTR&9W z{v(YP5SS-4*kGIbIfaS4kf_hQz-AIUNSdBDfL(jnmS8I4#TXe`qb?=<^o-_nv0AhM zEd)1K$nYuWe@;Ta*fL_X^FKeAMyuokYjj+kCG|j0#DyhFk}bZ)85dJZS38&s33K~b%e>;8{U!sew)uPW&}^iqmMj*$<595v3=v*%FEZ+ZTK=UMu3 z^&sIq%bBMuawIGkh((}Z&b0VYQ}lg0Kf?TtOBk5n<4(X_)L=y_`(OQ#Y^F^P-MtwP zTm_2M(~t^Vs!Y6QtJfU7_EfL=8xq$C_8Q~T8#>{WF8`??;vuxUySLys6~+Z4dKsO^ z7U5+`PIP{Zd`6dbMwc&;8z`h0-PY?5kS)*Xd}C3j`dHvui@>WzgUr<8CYRk(g(UP2YUI zr0p*@a^AxKI(&b>`aETCi7|d1OvEa-7-PFGLO!G|(rL!n4)>^-r*usHe4gCn-(KUI z9rg1!%DZm>pj@*ReDlP^l9btVzJU_G#x0N8&Y)15u z_Gl>j8^OHD7{4C1hX!GE25(uz!DeKxoS586M?La4SZ`gf<|mv?m%9t-u4V9eo}>Mt zSqXVg3HdA`&-$tfI(THJ%qX&?Xx?{2lHGQCPY4gh1h-MDi#)08GC6Fb<5zI^H3e{+a}X3~&Y(&h%${X7%73h zz&p+qA4W=ufIYq==WYih4mMdf>>AYl*NmxPU=gx1Foglt!zlR>s6~o(2lm>{h&GhK ztgM`y+IJ&`Ivh3q(1V_+Yn(+CYW;H85J8jvO4s} zSbo!uSW`NVgKzziKJOc)1CXAE`0Egl*DiRStX@4En)!DKU7Tcm9+HOf6{9fDhoVMlYazTr%5g6n z&==}zQH?pZ#yASJ5s+u}bb<3)GM;yr@;p)=wTqa%MCKvATwy313QYQ6Pza#pxf4Yn z`XdsQDpU(nPzU-0vqA@=kr19a_|K2-|7d+gDIyvl>K~yYU4Rchc~j)Z`t|9kTGvYm zYSdI|pKLzy|W|m|(&;u$WeoZRlX+ zT3?}0@}C#JH6t>oDRg^={|I(%sR&>dtq4MhN*~@w6!Gcs-x~DMrafL`U0&nsTp!yb z#R)bnvMSQ}s?6)m@C9(s@UsgcUFrvB04UCv4u%8J165Ucft)NniD?jxShOpHiFm;Q zSb0_zHPKxP!W4>`%Ab{jM%z3SZL*7FfVBOW*l%-(QCBzcGS1CxYb5@O|Igm>x>Q^`hTY zjc®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 literal 437712 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 z zPUPuhA+>3U-kGls4$&L))ng$|t{;aaoUF$e#`?}UTR-4apOnh~!Y@JmC%39p=1lR; zrt6>i)y?|1ezlkB#IK(5OPJK)L{fnTqIOr^>wC(t?)B^cw9_uK(}G<#ES~opOu%0P z`ng=GF@Ay8FXpOeL<=|N=4e{;Olv2CY9gHo$!If5xgi@&b2{^?hT=((jK|-y5pGJyyLC z(woPsALr{WV^#k!y3Zi_^{J6}OXS`$lKXFG38$wDH9QU%`U(4$ z@8hQI!V9JRRlX&|$2O>|^;Vy{U1azNpT>>Es$=Nx3FzIJk>2i_GCJeQ+?pQ8@rZ04xVx%%#0^&L!Au5t_9eSs~FYwB3%`Sjy{ zbdA-923KbnM*ISxj1uz zB%eFsiww@KTECvF-jd3FCrAG&SN%4JdH$tb{g&N7XH}bwLt7v~`45<}5_H&n*;mgr zaXT6{TD^w_9i?^$^(Kr?@D{^vaFlu{hgAjZl($Ez@8;>Bj8dN-qMsbC{y0QGHd=j? z)yinq%W7q``a?)RJX-xaU;lQLx`#E(D0Om}{>v!!&9HuBlzMZxguhatpC6?T6-fB= zBlP!2t4Br%{c9ui^P|-Rr%CuXr|HK=sl8%{J~dK5IZB$UOYX4k(76-dL^i+k>(_(o zQ5gbz1DZ2ScT4ZF#Cpq-BwG~Z3Y9+wF`D;^t?EvrD`nw!pHIJe6=s{8UL5^)lP9#z8%T|9}ne#fO^cC;4bAyV5aDE)J$EgnuxE` zoszchBWM9$gzfB%|dm5t{y1FWfJar`nIuZ#}FAj z+lT6h#^U{hej=nF9jo4x@}JAspB=0IlD|El{iF}*`^Tz#&TK@QXX)>m@x%(bSstns33#YFpt_gT@~sQJTwy)&q`iKJf* z=-olqe<+u*b5=NK%xmThs!xA4pnfWT*WCep4@2Pe-OvV<`+1-KWJ-@_$GT+~%=i%| z?WX~KZ&1YpZh?0FN38(<&?mF=Uwv;-#m@xwp_Jhmy67R$I4#em6dAfmwr0M-yqN%}90Qv1r>o zR2FUIKK;ybwI~6F`^J68r~XS!8t9=#O?Ux5{i&29s+%GFqF;YwxY{1L@w>y-Q$hX8 zaP=3hj}BLN=INJ)t0%=7cqd;U98M3DxI)`p-V~ zJ?T=`g0~2M&Mu$+`{nA9Gw!F|o5twdO4Oc0y`@C`;2izg67|D~{z{2jf1bX-MD?Dp zzf_`rbH3hPqT&}`jtgHT=>3=clB~Zk)&rLd!_jB&A&PJCgkOE#uix~G=WEeZjC8Yi zNUEAO_gZ7e1t<6W*ZUH9Ujpw-;C%_4S^^!n+Gz#rZP?(zh=aGW?9#+A?_32zv9wA> z9Js@QOC7i$zr=eTb@C}Ey}^M89JtMak(|_gW-f!#@qmr5aOn0saJ>uD@{N%>kn6CM zuf(C-CFE={XviIcBkpi9I@M6I0sZ<6R<)u*BU2X9@ES9M%acN!!nm!o_p5BK`Zvi&2_Zh z=ImO{5;@<^#g3Y}+_Nn1JILyiRQxOF+HtquMNYtBW6pvJ#YxL<4?#o0PbZr8)kY*$ z)=YNXX^!IO6is@~Id(d~!D!+UpVj$ z2fpn{FY{;-C$_yvvd2a<%flh4*WDALM<4_uF`XEAMyn{xROa$osv# z-^crJct35kzVETTU&4DU?^}4kmiIy4CwRY&_qXzXH}4tQY16ao*B~o# zX?!cUWa+~X|oMZ~{Cy!{ml927xt$@zoA=PuG8Q8+3S-*>qJlKqiuKk!qc3!}OT z1sylNxV-mIcIw~qT@ATpAD!TJQ|?c9CbazPEx>E(^Dfd)U#Ij>#ouxv(dYOax?cbG z^4~}N)EmPH!}|Ag^%h-2{0`z7;vWQFt6U!^{nto;2kHNL3Hl$9es@6$eVlhb zq85>7!r<2ve}%!{Nc{E0;hfxc5O^&)KMY*jZ#~;D)!@F(Ab2}*2;r{plmA!9AJyTm z^=Bxb_DMxx9`5P}F7kha^xud5$@PBX-Ba#QIR(aDk3>LOOV0Cw3!jgSD&12^{~Y4~ zOx(A}mn^~ORiwX|9{j1~b2srW;x6dJZ(kz*X+!@r;y)lRXK%@MF#_OP@;3n&`P*Nt zbXC1Y*Af2+aWBs#@qZ!y@1*}L;&WvMe0==_;`b8YOZvydkmSAFvGPo=$575|i1$ec zf$IvrMf-`rpSYLjr6_N$a@_;^Q;?BA$ng>Wbk}#t=UayV<1t_nd7dz#be{k7h;K3Y z^~A>veiQIo^4tbo5qv{6^p+&pehH z$#ow6TbcN=Pb>aoigg?Dg}+x^rkmw@JMs3zieq(>yFN%f`ikQ2EK>aP_a6Vc;v1Aa z`Z93Q=k)89G2FGg?jgS4;6EaM3vutae@}eU&>ts0W#}Jrc5OXBVhQ|w;G(za3Ux?% z%DJ9+iTDulKH>|+y__#2evJ63)Z05f|Lw~E6#Bh?Bwp980GoI8uf(T`yK`&s?uTFr zzR2nGyicP%=M(pN;HMLBChqgZk0-vJcsujQ?u;7zwh#De(YbNH!q?aBq@UiYa(E#1JxcqrD(W6!V_UjdQXU^dFrxEvgYkx+}X5yurl-`{a1NvT1 z{}RQw$V+hTCw`2$J0k}8`-v}1DLuP^=w9NzH!JSWfdRc7N-uiuzC-ah3v*o0evIPB zh`aM!fES3j|E1DAFIW(xkv9OevmlAA$NU=eCmIsd_GD1 z$HW(iuh(WDJqj*Q^j804rDqr&H4&fs55?VCBj9xn@r9o#{!+a~FDKsnGX?gM|NA`s z&lPuPfPl}pfxCY8Ct5C4pS#X~oXXSqKT7Y;_`olh0T=pu(oZRQbPe&DpDLf15+C*S z2FLm%$7jayxr6uu^;u8(u~gB~&v~5uzXe?MHuE@j7_#j^F08V2_|*A|yR$)npO22- z+2@6dyR$!lKaY6(6BT#oeE=UOK7Enm@1w)|0C3U6!hb5T8MJc!9q}3B?GDAt6wm(> z<>Sutz;BO1qF4B|lm05wKNGmKhi59iJF^47T|@e(klx$jD~UgmIEF>;`Y`$Qw(-4` z|M$e(pQpGxmjh31K40Hk+M@Vd^cIzf*Il8&+=a^j&wz{khgdJ(?|l`x_(Om3wb4(A z`@jAD;Ye^xe|Nz2N6&Eh)x>@L>hW3fzr*l90$lVwKcwYy zdy4UfKM-FSRvat0-PMGSK_(Kl7TAzmW&47e41jc7EVihhGr+c^RRV>mBtG-n#hu0PwS+=eAdje(ie~16Akk6q%O~3cGz%PP64>SI6 z<3rzZeBkHbdx}Hk8wqkXJxS#$y)MmXGx7SDr|}zsKM8XBbq2~Uiatd8{f2&?c=RV} zKHn!kJC(+N2fUV^FM6`dIcfN;2QKo|->s6|qs5BGNI!4*-v(Ut-1yj29p!(M{+-0T zk1L<^Sg^l!IEjMWFUWs*Ut0b%FIG7Z8vG*S-LFp5Zv!s!9NYL%$NhDD#~A5X-Ik_* z9r4*$rt!OpuQB%UDd56?ih<8%h}6Q(qfx1n%sg>u)v_za6;fq0#X90P*4d4|9~hz5gTm_;ofu z?p^a#eQ#q<_2b7`&jl|0BhyZ|k$&NqD(61RGfnzkuSvgmmiPf9&p!~~`l>Yj_Z)vR z4Q{`1IP!SwwO-cnH~HX`$XPP{F90rj^Xrhj{m6N`LVx_G^n2d`yp|t$yTj3sQ$N^{ z;;t_&L4VH@_^*MBJdIp0Fh}{Pf#JxX6Fh=)AU~;zQ(kRe<7c`S12F- zZxsEU^zFu<|AF{H)9x<7z+dECFn*&Ec&&1ck-pv7=gWz&GVSy&jt}C@r)j&GqM&yz z!T;;zv(?D+OOM}@*3Uzpq4nNfQ2w8%pLq)L`Lpzex2m~E8;G~xuDE<#Os*{P>8j#e z6^@Po7yY!E_WNJN8;w5YsB)n{V(8B&9+~#F7I-Z^-wGU2b@W2UX%r`tquXoIe|HI7 zj;^mo|4iT_=K&-CWyI@^9cGEIG5Vh%-u=>ay}Syz^k1b7s`!Vq-rwi>8~w}?pELd1 zJ;aY0Kl3Bv?MD7npQYv6Z{&Fla3@c%3NlKCK9zX=Rcb%(>=OLeK|aTfoYxVbHF4vg zkk8Z=%Ez6P0zL;k{#t!6nxVTsKt6{}zyEpQ;@|xG(KAWEx>3t@)Y#8+fY++u8-NR+ z=#Bbbs^6W<>+0RKuTPTyf{Am!LVT;y!%v7GH+ud9@zTv{dw6t4_1`YLfN?#G0?FCD zBF`>Uu8qXE8vZ*yy|KeQ`PZ{w!*JVO`$#`!7Gbv&5&3e*S~_ zQ4>GZHEFr(j6GirT>2xwKG~0(Dx_~Wa=wZ9L8FIv5npZE#ixNgJMT~h%Xhuy`l;t{ z{PP*jD(8&pr=AS_9DOwP-@T;YWy*Uy@p)5^e@%SW*!h>2;Qw9Hujcr772kfG_%5UW z3tHGNjDBAC>L{v*|Ex3q^O>YSX8NxUz}>iy^IY5b-ZzoH+tj0crcUg(&h*Rw0KAso z?j!vnQ@>}d)%u!hQa{?IrmCf%FFrpYzr!|AVGpE+HP7`n`>Fbp~YEVmlqmoA#I-Gw5w>IEX zu3l5FE6Hc}`O0TCpUEx3=VhdCH+J|n@~=1bf zPyPra-SsNqLciMNJq{9|Hg^75@~Qv1D)M9G^F!jZ&r*5ZIhFYBkHjO!z4|N8K7+td z;h1Hi!*@MT@dcc`A=f?bzc`NyxRe)L(H#=^N5I9N zySbojfPQ-o5;d+KjhtJ73;n`Y)%@$p|3=^yg2^M8_l<|UE4N^AhXB>kj` zqt4u*^30lXw45#J%KI8E(2JB^lqLNk;~!p5e1P`j+ufVVr~8|D8t`5ZR-KSq4c*x{;8THapMADu^hwecq{z~#NYXX|^v$oF=VzRtwM zL!_UhKfi+Xdr5!Ty!X|luX~B|AJkj)Po$qW<@!(JvnGB%bF-Ff%GB2dz(xMXX0;sO z4>oyv(_dUc`l(Bm{wm6oBi_w+>ig@L5TALo5?)REzXC3P?5NStryM<%65JNZCwlg& z4!wxK{fc~!8NJCF0m6TkvBOJ%*J|(Gq~B%ATl9R4o!mxz#@OeKLl4l{eN=_{`UcQaTga3lRRp> zT;)7s`h_0gVm~GJgAZeQ`-L8dvDtkCCGwdyapr5tXO;$XyH<1b*T7x>#r>(?|NkfX zOqzO^k79^Cv$VrUQl3r3yI-dzI*s^^#M?VmKP@b9nfL*DBXD;25R6b{=69 zaFKJ$^y7oTWjyHjCHgr0&&j8k_V8$BAI*_Zz3F$pNqoVy>;E9W+R*=j{HM37{Ewpj zJQjh6$hpSQKMS~&YyN+f@V_6T0&OGxF4L|DNI&y7rT-N9-%k2@<2U|>c&{1n943Cu z#J&GU{HT%he~2%bIOj24D*wy|wYS$%pU(j9?8n&2mBeQZ{vzVtrXEYgw;K7Uh#xWa z@)y8eyEvkPuz5u9Bffy+zT~3XqQ57fVdE!{5Z`ac9ls<#YwBec5;#)c<3^w71Fz+` zpH2FMrhRQA-fPM=M7+-E|K&^Yzn%13jr<=WzS{JIUjZ(D)t?)1rm8P`+IE)9*h4$< zIg(iP#n|$8o`I!Fq)4*Mxllh-1(*K(D zM@_ppf-%EBC@LKWYpOVjhQ}6E z*zA%mtPUUGccJegh2TZv(0v9=F80Vi$mail}&2f## z-$y>}W_+~B;IKJ=d*~{nYem0@j26ub`f7? z_!odnyRiAmzW^@!nP$B_n-1tx-r|LuBMH65nO? z`D)_RMnCT)zTfztj{}!-%`;yFkL<25lfK^g)t>?v{mgJ&d{N{_eBAXm;`8;Y-~sag5b+u2i?EE@U7scYZqqKlL;AVPl)oSEpY;NL@6@fz z|GoNi^jzW#f2uf_OGZ}%7yZwecD;xAAyckfh<6+R_AcNOr}^`%@Gp1$n)JtwJv{1# zwe8_az(t;VVOT>2>{1%7v;o$aB^6#a+c{@4#M%IhT4?GEYt$w71^lOaXwh|vU`o96V zl*`5^ZzlaN^WJw8KV;(kzxDhL{nyFA&g7Ra%4)f$j6Zoc@m}KxdWbg~{$s?a4gFh) z*BQSzOMI1SNAtj?o%Z4&W4WHny7{5Q`A2a3mFHvp#<{(!Pk&yTug3~;8`u2Cwm{mS3w51-?3r<&A%c9MR9^RSOR zReydv@nf&lzaPf|-p7E8{Tw!a`=31hGgKa*C;Z=}KV;g)X#>hoW;boa+C-Q@@`ee>)HOW#Z98Q~_UmxW4Q6z(vnfw>{JWa;}P8m*iFc zIV1mO;>WF>6Q42d>!qIF*wq_}*BLv15Aj}u{{!)odG7*nXK!Ym#_veqZuE2ZpqA^1 z@wb-}k4(K^2He$Go9buEkuJKE^1DF2YpEK$E_aBmewTS~C zIz;<4{4XTlZS->$#vQf#waZA~ZtA6%c)h`I0WNx|WBxxwJ>2czYsL$oCw|EIhkJp$ z{?YVb&lpzzvz@AEq}SZFh4?hrhxoYhg}|kq&KSR1A^m*2+Vf}0XNL5{Mn9hG5D*Aw;BF-6Q48o z`32&uO`P^U;8MSKKg%yj|3V`tJ}gz!t{wwiqq|A+aEG9yR)$el_(lrO#iYx9F*$ z7ydJbf0lgaj2%t@7k!q@eA9l?A25FPot}?rN1t?jC`54kD(QC_z5U$LqklP99r;;j z>O*Ia)UL0`0~bBa8vQgAUt`)u5Ak8+fAYi+m^knz;@w8icX-_B?GwZg8~=O{@nc5L z9|0FV&plbo`#8Nt4)e$dEyocK{ge@;pHTx|TtCB)|ppXU-kYV6?#;={&|?Ik{G{O~mKUSpph zAYO0$$rp(4H}UXKiSIJ`E_{K)*_&NQaUO74H#PfB?RR`Xz81LXbG50jZsN}{`WYbq z($BQM-o<(^5|92_fnT#8XMu~I)DNrQYa{(1$$!qY#|z3TPn#JpJeT-UqqpmbH=6eQ z65_i|y}zCK0b_@Uh|e2&zCpa*)Z;IR?>FuJ;T4sC%GBe9#21YGjl?I7U)=!w9O^2( z?I8VXV}~W+qUYf~s^@w>^cm7G82h=8_*N5doL*J=yG^}cKs+*fYaxEn=zkmW!=@kU z^SG&(n}ECcjd`;pl>hCd-(~#Lhl$S^y?vE@8e7%QA47lpebVnY^uHuNY4mdz5i66U=;73dM^StFnG1l69wMWMwZwOsdcT@@ozZ_)_)A>3Q5{Cz zY5Jb8kbctW?Z?F1t>4?raU%EU?xMI=;KJXo-+MLjM()4<1nJ*Pd?usvJel|$a9Quv zYxaYD#nF=}xUHF#_b$G60T;cs89ONwpET|I6^@ULYn0cPXX*XTq(5Nv|8COHQ+~Kv zcYTWV$Bh2JMfzE@ZmoWw%6Z7N(`N%0eU?bi@GZKH^hb<52OK?x2yS|83$khX1dLA2jXgkuRw&{}X_V-nJS( z&m%r%?DiVq((m~5=<6u|e$pQ>_I3yGT6+5^>Dx`e^CjYq=DiEV51R4pFNkk7e(y0a zRlA*Dr|tSo_6v=`CI0L-dHCCaiyn@ce&+!B)Q@WaBH!Vb>j?42$0`3+3P;hOD4$)X zT#o@>OAk*aeVeKGXA?hW>isIIGg){tuIW+VJ@@aCxu)&WQILPkgz)x6#yN2XN=No3)}&JwpY(oAm8QKZl5~ zG4=IT;;Rh(k37B6=Wl_#{>Ze)GpAI}S<^3HOnlDxjpq?>GyUle#E%%iH$r^a(7zJ6 z$kYA=wJRUbe3;SjNx#sm z^k=A8(Y2)SHT5{^=#^*q-#3y^^j$@NqhV`wn0$_ydifslDfYjocxJCq`Rm$MkQefY z2H+z99O>(bZzg`sl&c@O*r)%Vk8c@9kwym#@{;&AMjH}&2HT84hCq8ZD`Dfzo#t;9T_>A$Z54%P6Hf-iWE+F1$;;v@m zbEdstNxaS2=WgIK4)xzPVK@`r3EZ_i?q_)+%lkd@X*B-xm!7|o^YmL){#n!BR|Bu* zuW}B@`iJ+bft=3w?kAsmQ{EZU&oO`dNYWo7eaYzmUgF1%|NNQb!-s?0DYsD%jNcx| z-yQ>8?77YKhM57{`0%!Q!@R_ zZ-~#D_Eq;PmaC}p$hqxujX7NZ9sc(-q(AlxrQgXPPuZ_}IA+Rw0r6=QuQmX$Rqq=d zj&T$7pZb<4>LH(Dsun^hs91xMn9{GA29W@4!HQeMic+M2DqFbV)wuQndfiX*FoZwro5jb z9vOSP7r3Ep?ZfQ$ZnO?jJ0U$^t2PLu-q z?{v7AF1Zhq&#Z|DUPChe_(X9?o%IW`gfPCsr`~4vCLx%sCiT9dv{TuN{W9P?#*YX4B{wdpw zY44X3?>7D8^NG)!{`VT<3x>}g&&T-fR}$ZE`20EXLx#^Mh)yvBSxPW0vGx@vmWw#(jPPa{ME!)8~-yyyxzoVAM$(*{bAx;O}V~Je8!aPKZ%#j zIPUZ}sQgnVU$FzY*m=E?e-H6qqvux;?>6mpn)sBF^L@m3nK=J*z(t=km#Llql=}Z3 z>37<=h4?{Z&ySr}`H!3WY9M~Z*iRSngGSH0f!DJ0mymwmw2Nut)21E|F2U!sq%Rr0 z%@dDIxqjyOz^`6SzeoM7dZX&Cu|fSO(t7S{1upeCZ0da*@yL|-1?1Dqd4GKi&gCZk zwCNZAlJqkTO8E%-;ZKwPu#x|NIeH2g-0I$>a!wk1coOk8)Bmm~zQ**MR}=3w`rl3b zsPR8H1DEl>|IVl%?|%!p^lSB|U%TYZDrcQ(7h8d!6O_mQ^9IsS8~T?zdiWXE<1ioi zn5Q>-J52f#>t%p?KJzUq&tYTF7ZQ(5yL%q-Lq-offlGP2cPeADj}DT4#>63C050)> zA3yr?-b4CX(~le@e!$q_8Mmw5&R?FkpCBp}DF7>#7kH#}o6zFy2 z-^hK?9QH?_AU@0W!+u^s9~EIQ@XiQ!;vc3UGH$&bQPawo#r5(oY%rUkALFKJO&`5#v`s zO}x?A&%KTh^uYCga8vI3Iq8p^_ISqIYQOh9;9|FtvD+5nt4+VWbqPMNAbqzf?>m;D z{|D0d8b9-+CFp;<1itm{+K!@vwxc%c;cdWOdo+28zX2}xWB2_XCjCKE-q*q`h0iJz zZ-1Bc^(Lw^^H$(e-ss;|G2f`y z`yHh3HT?gE^!3bt-avhRj(DSKFKu^dxn_<0UBuTIzugC1vp#Hmy&+J(cA67 z#n0Pwr;n1p-n{n@#Ai%l2pYgrGrM=7=`}{QNyPvCmaxLrqFzFkO-hK#N}m|KjVyCGZSzk+Wp@TtR%n^i%!BR~dVI1@Sr4?%odkE<_yDJO{om((4Pr zYsvXT;37|C>i2iV4;eYny-Vp282TpQQjc{N)!W69UautHd$!gu)A!L|5TECK!D`Zf z)YHF1`Fu)m(a(X4oJUN2d-^*SKWyyqnZQM!UhZ>zI{9xQeY=f&i7yzteHHPorhk72 z@#Fk{md}U$CGlYs&&&ZAd2Al<8^FchjxnD62IV~cU0SXgQ{E>MKVtgX=MkSa@$C-c zTTOkHJs(rvHxi#Q@_&H%al?NOxRh&(anWNa|3lub^0yiLypZ@B(_gm$uVtSvT>}3& z`5ZOvrPdL{`x)o-g;9n4?C!MyLs;=#CuIY_g? zUa9hYOK;Hu(l;7;-b1|3y!SK2+fCf^PsHbqUHu2~UZbCL|B8Arcq8#CQ;$~w7x^3a ztKcuBeHKVRY5L!n1Ft7dczXxw51IDzao}Q!92k!L7{@0HejuW3BQ+Z}d z-}zoG*MgDfMZks6Y`@aqak@V6QPNMD_VrETYYhG~@|n5zp^m}LXDXjZzmM-V^>r!m zTJ5o&^t(*^y}{AL-+oH@o`x<(u05n5HuZZ8@x!LR?j#?3KKv}{5197;_rw>BJ^U}? zjYiI&kpCR#K@g$3>zVIYJ+v9Wv4MEWw3i;>we)j0=~o;7@VCHST*3I~48Hfm59oV) zO*`5|e8I#gJBhcM_<0bxvp4R?c@p`*ocL6|+RvrLKTN!ic7@@vyT0Q2T%`0L)m!uv z;Ntfhjh{L5gNh$Fe&eacx0-ryCq8BTz%Gv)y%mX18vWlwyw}j*L43~G)n|wwGX4H{ zfV+8Nj-#)l9vc2y^=Zc=&j;?}C8LKMiLWx{-AlaNw8sOU-n6?};9@5c?S$%&PJ^S6 z_YNCA=M$edaqqK;cN_g|CBDYk+W_&%$p2E{VpmgK5AZbV=Z&PFGyd=cz(szWA3Nnk zI$qv2{4mGCkC%4>mvYUT_B&4es43U$fQvkKzsx%wPT_*vmzKc)mHZDIeg2a8G2>^> z`WuzA&G?fi0~dMf8UL)|ySEazd9z;fnKbQslJqlXeD~+1pEB}%i1;yMhyUpK1Pb6k zzYARI#eTQwoDXZcW=y+yy2p(@JeT-ULw_}J(Stn~ZiMu`reC|6_=52pcahK3OV#rI zx|Cm&ewB%1AMp{Df0vQxDZuL&t08IuE^VK4coDL(lIZM36c*e)2pCo?7 z*wr_QPZ~SF54iXZJ3hVO6DohbY1eCsFBtuA0q)uz<8Xu%?&>3M^Y(jzOTDaNy-bn* z@5z7O*wwdzi=1;NzFPN5mGgipZ#VHe6Nd}}cXGZ#>uW%7(LvHrn);d}{S?TQP?LF0X*=+yhWG0ho>dRFM{n<*jTo@bb?C+^&GX2B3 za<*E|6{?la{*Im;{?oCksk5g!+tJZ|W&e&V@vnyd;#j3xDi=!)jSoIYa32{Ew(GOm zp|No%YEw2_EoLVv=t&fz1I5^x?cP%8-r1NP?Y^?7E!&-G&Soca*^xq}D!S?z?b(>U z$&@X#erRNHZ#G{p7t7iH+{j2@u76JfittiqGKH~1HM_SkHc;F<;n5p1nR0$?AYaao z6pN*aj!bK%nyY39%em2fw!b(&Rz(5RH2%?b{UiC@*mx=Cg*qwcbJcwI#pAhw9anB| z9vT^Kp2+uib`_dC2D`TRWY^=Dk-}ghU+!q^=;-Ka$;SWN?b&7;$px0MLMF+x>ni!` zcnN~X#zg5xbA>U!;qtDlHg4$3W-H@;+0N_`H4>>B@?~#*&AD>EZHLoMzMN?kZFTBu=uJ zuaG>YH~-99QB`5Yt15ev_K|K5o_THg)%Q9>C1F!W40L>~Fjy>)!lCu&s)gd%$;wi* zoQ?2s1KB|-=}8u`7TXrFwv*&%&C4sTDYW2|21v6fj$e51pb=z#pssW&AtfO9}G zjq^a&{A6{!obO1B>}hI+0WKu)l4?QAxqXOk`_rPX-;vw5)1ga9Bg#vNmMBp|Ad#K) zJOkOjiDBm?d|;B&cJM$WA}qvLV}peurO&L5W!j|hQjfOLV>lm?9mrL4>O+@#xApQ{7GtM4(4=WP z4^5%hwnhG5tzP_fJJp1}{JVFHw9Q1fpnJ_#)AGhoXeA@an7E&Z_`=p$U;c4)4zMru zukiU!Ywd7!}^BkJ3$Zf)4Z#h?Tk7p?Ibppf>(}~3|Z5=|dRhFU4XrVNkD^&u0 zN{fmKT}O@4QE^oL{K+}izH~);?pl)!fzTeqZnSyhwOehMy90GXGwJAQO2@N8zt**E zG@PDlD~4g!V!3m%A+z_z_!&bn?<6rg@5>Jr#(FSB$KU|}te`%!)zNJKNO3G*!MJ?@ z-Tp|iFE^4M0OLwFH$I6m{Ag(;U(FB5XuB2#lr~n#=F0GG+5A|wyf4y$=D_&q=sr9W z{N*~?6lp((q~)AbitE(fzhH+hIBap zToY?v^lS_0B+x`ajh7YrF7Jxu&6nq^-9R=B3}En9>8_Te(cC0L0t~MZ`{Z^P%hA~Q zXci5tk}cr3s#b8N+LtYiqW!bFyP7h93Ryfd&@6+%3da5;g@FPt*m_rHz5ZOzqm>|E z;<_%R2o!~9mL~29TOdbHY9w1O?#*VphkLR;S!L4H zrb@eqQ*WBcm#YQjS+av8PC)9F#ZKvL7>dnS3Zny|)|yyh*Kg|EgqBsz?zns-iMp>G zhNr^+Sm9JtVW1zQZ^x$4jV1_FSVzqdAmvjW6Op+rLm!GZlpAvgDsbGV*_ncE*xuDZNL@LJa8Akr7v z-KG2x3NTuNZyBpry3q+Z%M-jHK~y+Y?P}t$H<`+&8Q$b;`$|?Al{AW+Sh3%=D&Lfv zD1;a&M5{=~You7N=K4nRvGMro-_VjBW+k&iN@Js0QEztdaG{#7ln|^fdaB=P#p_S& zh+08KNv_98I>I+w0pR>au%yH{_tL zwGI}_$TEt<&`#JfqOA_(OV#0Q1y-L&9ui8DAAMy(;x?D`nEANpe5I6)U1>5?N*s1n_Nc&WBgD@OJ+RWb6`Iv#c^g~Fa7W*|HmL2F% zJ<*N&q=c+xRX3URjpizQoI*X{_#h#CQp)IEcSzR?oh@T)t-hNvze;{`#Oald+5E2Ib-JRK7QQ3XTo`kK zVzz&He9T#?xBc<4QaL{g1r|yp`?AQ0x(J!lHPhZ}6U9O{vxv3HVp(JFRj6b$tuWNW zA_pu*_C8K}-rD;0H3N!ncDu2XT_bjl#6KG}dNu^_MA;B@WKoN}qq#iP=VbD67L2T> z;Hf@=TBApC!nn>g)!rT3BOn(s8jx*g_X60+Arqf7un!`;a z3}P0+L%Gq>97bm_QQvsn3lZgJOGP)8>>O|##yN}X(0eWSH0{F=6VUEtps=X%(M*>j zOWIX4t#c{LxS{%{C2|kmuN`aY$#G8*yYR`&SL&-<-|jugkYzlkM0_aO>tBQFin7 z+c#{(7mBk?Y+toyOAn^pcW&6&)sc+`t5}85etB2t#!cC#hNgzKZfuWS$9AM22DZq& zxCR_M(D!Y0gKsEjXHQSH30=8NJ!8&XCpXY1l}0cq=yVeVoNz#*Wul`iy=0)vg(Jn0 zLjS&wrj8E(#%nR2%TI2oV$nlichh)5M#qkopW>-8FD4{C#b(S}cJym0cJ0bGH?(-3 z8<7xHsVh5jV?z?Q?Y#EEl--M{6CV%ORD>6Q6K06Zn`DsYksgF}V*|PJK8JQUkBe1f zg5^Q0jm?>k3IDPk2t!8mNWhO(WMsP~pC6EA7XIgJbmlPw86+VDqd`JQ=3M!M3WpqA z+xS?2ZX6R+2;KWJsKPAeM6KFl^0EgRHYA9eH*3+BO6EzXGDzpiLYU2%%34Ae8LT*K zN)qPDsssO9Jk65KBv6L_;c{`T*qpt#DbrNhhxDM!4fgM89xLwcNEN0bv)+pkT6Aa2 zcwa1A>WAi7eVZ{-U8L=vDu$AyoyZBuxMLu4xdIs`VHD*egR!Fa^BfuYmtUT*_PBId zxjQG#7>UD9v?UN?&>k$Uh6kjnRUGVx_9gc_#{=l1b*lyj-wfl7J&y23k~cybK?SHAs_QxOmyk zrVQtpTKqzsO*+M-Mu-42@VGL1?Yw`_+SHQWhIF4>W|AGrA=((%qz@)g0@SNR3zn>tMV-Tryu)Ta^}4&I?!yc>S>JME2>3M3{zQ|LRa3Esy-1`mW%rm-G!!`sYuNa?uAKS zna3Q2`otAXxA>=a7DlBPg;&pHx8^EHf$u{ci|Mb?(e_Qdc5T?$nawmbBaU#U;(fQP zvUQlG*^Y#StX@RG)6tPx=j>B6mAO(0j;HeqER>trT)Jv!R$H}9Z`B-x;RVu_uSYw; zQprLQ%OXXn^g79oTHOrFzOh(@ePMDYZU>=~x^%{mZ&;>lZQbN1YNSxhMmnOC;z*^V z8F+V4sqgE|%mh7$b z7~WAs(bc>-y8;@X>oQz$o@o_Z#jIflt67tGVu9^CjOvT9%Gxhj#? z+mS|wS!L`ENj4-Al zT29geyc-1zEM;x!kpIha*7eqv`ck?m)%M&;PtsKw%U@R*Kth!jp?V7Yb?9k>bUu07 zHnbvT4%Q8^nPW^UOQN;W)M_zkg;-H6W*FLpTd9zj+?-*%!iCnYg)ywysmeNr)G&i9 zAYeKzYB+_#rA1pL74$Kz`x8@DH>%eGQ{6nb)G8p1#ktd-pKW<&?wF&(lEN#We3Ta*Bg(7r&}7VF_IsZ2{i;)1vIYeCbyou7T3}I zXc4pVzRAIrx>0j2!*IaRrgj%DaNijmfLFjEq;{snGuoxK^Y}4qn@a-pws* z5dlNHR%_0z4dX&tX1ICm{sTi3cTu$rO5&(KX~Lr24YG_O4uWd7c}&)j6)PG;!&sq= zfT356UPW;AS*kLaBD(ofq7%eOZiUJ=w?M_PZArL_(L;4f9~j46!8q0lt6B{#PlxQG zo2Br6@Zf^<{J>ybgtU=yKqmg)kIuZDkjnbQ8d{Z3Plvh~!XTQ#a+PIb4IS>TB+_6} zYFATSAm1LLK411A3FAhrY$Ikt_L8f(J)cd*(mvXg@9Q)1c!;l!BN%8n?d@0Sme(VE z)UZ*uz>J11Lp(E!gvxLu95nzt&IuxX%IlvK4}f2Xq@zO0O2@N;P$NxAMQn1>it-cVSU%%+0^=_KT9nv%_~y;wNWF$BbTP{8sm8uY zaxJ08J-Dt-Ji&_eAEnr`#z`g|jgJG#Ux!c({&$4jFlOD(08dgsnCmTA*iVP&CA zY6PQJ?M82~F~9ao8qcBta2)F?PBH-8?Tev$?rsUZxcFNdtMj=CeXwh-itHF>rV($r zv0P2N2!n<0qOA6albX&;rC+LP;I5XWL39^K_6-%swqXG2Ndv#GE<}tg4xrIl>T1|p z*%YBPJ`LHI*bSmf3A$qczH7!tYvP-c!N^tnhb0w+{WGyOXtO=xw2gmVP&^YX!(3~a zs&^tx37_zLYiAEo<7B)MlP-HuxFmB`Yt^ooK2_h&gpwhbTT$wRuxz1H>_^8}!AhI4 zL3Js}qcpd9PEHSQI5()nHknAr60#9l_98P_nD^+P%w^pgPHYB5O$4b3+d5>=9HuHx zwsFKE4#I~*|5c@!ecMj#+87%e$!D5|@CIxQK^CSEWZJ}Y(a*#W(Pz6M8`i9<#h&<} z-4EGx_hHlh57*c`u~M}YZk}vPTpnsXtUTAwP0lToDdo$RlFJz8I|gwD18SM=O4YEA zY<2Y7B-2iII@rA9$^-{D!hQC{ekrj0R-rm!Z&9r6Vu`$byBxzQD!RQ^_ISvU(i3`T=a3Ey9vDeQ%Akb(y^a-ru>N zM_Kf_8CQ-Hw28?yhizWV4jhu5V=EXuWMtYveAs|XkeAVoBF|dIMh>^^$cq_vLa|o_ z$cWOHK7KVO&cHK2imV`%Bz^Pp*?Rvj4X8MuCCR(;*b+@V)xO>MlP*H{*tr=(B1SSy zk)f#-`a9yOLT<~`JwVLzn%06X2^{oypuj7L(oc8@$1X#I$s{RIY^}I01vb^?)oy~D z2YH9Dua|UR;?ync9Q@B=WwdM`@ALQ$SCkglTFdJh!-=Pm6RJ zPVMX1nCj{-l=9f{t#fJ8c}b2voQL4+L;x0wQP%_9hU}#ei`f(_7iFS=GFez2#KM?F z1DzFL*Ktgo4wp2l!bMNzFgXfTo~f|)Wnl%?3EF7M4VH=qAusueSfzb zy~6HfB~HAy#A!_SQ@Q0<`KH3aTIpO7)M|9a#Wvnn=&Hi1^2(0~&Cp9QvWiDBu5!?g zHTuD%uEEv=K1|l#D?Y6q4P=8bHmny6B>hv)>LKZ-VzG>+xDoFhwIh!ApY0NXU!dT4uU7%gsu! z>zTC@&P`xZj10XnZH!S8Rl)EjOn#)>Rdd{OSPvNU*6{n8Bva#y9?v+bUvjT7!Y0!@ zwQGhN9o)Muw%CWdb^tw6!xWHcYEBRUVeL;{+=&q!-cpJuSFlWz36o+Bml9o!kJ8iY$ zGCm#Z`8UTF%*Nz$Z%F`w=`jro<9LIXArgBYaa>CAOl3iMO#G zjEgMu4O;8InwWnL`yExeTNq4zTr1nDQH|7#JR8myG-G-sNqmPj*4g6Pm8`PLob+m? ztx&wLM(2NB#RY9GP_~;Eh^rw<=_ji<7nf8$d}k(X8+86~^JQ$v#@ImK8A)GLOs~_c zX)!)tXihSsDXnDUWJlW4{Bui$ppsxjJQ!`ht^o@c8*ql+C*4~TJfu|Jl&HE`+KgpjLzkmQvHj>v4z{f;&}D^voFcbi*7v@uu4a>DY)eLIi>#(H`FSWCYx zi9dnm_;%&mlYcCnQLeST#2qN2W_D8JlVFB3RBFq19i+H!p=HB{jT@1x9d|hk97&ST zp?lD{y^@uww;FRhv|MC~bijzDmzxTGfTQ130~h_dJVX_919s%;L{FyR+|iAFSpPME zvu(U~)0F}@p2cx#eR9kyj)!pjvtq8zEz#nvVUuUXFvt%huzH8p9xAod4gL7Trty)H zEl2@*LKwi{^mdw8MXe2C z@#M7z2fS&{xCPeY{E=ELOs7WQvcKaW~l1K(THb$%& zkZfm6=(gRYwt8vb!VcE+b5gm5jmh$fEl7{#eFA#v!;ho`(m|e1>$%=XJOX>4Wb;#U z2%Ueet#jn!!jQ3H{WIqV2eRws_$tOf3d0NTk=vjv-Z{DZo^!v7ss6 zT&3L%;}|zQ@(~sdIUFIRw-E`U3v{U5IKu9%1QC|ki%)@vIpwq~i31RwDqpsXTyYjm zS7gNdV^}aOu+=2IuN|mfm$l&(rmkFHUbfk5jPGU7w4kYAVg>7CW%3Q#-`&`O;Y%2v znd_niT7+cMUDk##)~N)Vv@QoFr61dC%R~waVU9bHZ1H(!v21M2O>XU{%T1NaMYjnF zTlV3uQfaC<@fFqr${n_^t|W@YFRY^4+ZRN5jvSJJV)^x#ClGDm&jVoKY&LpdCSvpBR+kbWc%VlHL@XHeO`FlPb|h<#!M?pJey# zU9$E$&DcTEyu^|en9oV1tX#8*k5BfBiZvYrXy$PXm+V+NU|fp>*oxSTH-KdVa!hy_ zE;clYP9E%rY=`*_Z;%<0B`c+Uu|;GShaWB3cvEw-9!iDuvpCRX>a5wNMjSyDNC|?_ zG;C0^^3N;NQ$E-(E^N;dSEaiLIv%@`HBKc_s#@;9#sZ2UljDok0Tc0aJ$8RHyXe5* zF`ew49ut%puVMd=tPIvA!C}fH-PU|ZA^Kj4eKD+F$=)*UDq;&;5%&|=t{^sJBjToP zyvAA+Rzc?(cd78iTpAr`V_0d4AM30zU(V`Aqt!qv+-F-$lfimv>C@x&A>_jsWuWae zV(P&S9v00BNsn1^2g0AE8}u)w4@wUo!*&*|a)r9Av4<&#bezfE|Cpd%+b^SDY<+6O zL6v(82;e&U{p%ShxuJwuUCJ$lNddguukF?GpioK-(A0|DS^xM5k{9v1Mtr$KMpv>9 zAnk>ezh4aL@*GR<^+di`e)AhXDI9xQx0X+r2jaz(g+;TCB3`n9V;#QeuqUf)?^d*k zL)LNNV-HJxNW!ZRS_wQrr!0TX4`Amn$HZ8LF(60(xIu9;oke>~Qnqn}aNe6!X*#SH z7M%W17`O(!l0A?uZigbK$l?B*vz^!}bQQKNj$+AMt-c?nN#(3uW|YFFOmcfLaml4q z!C1k^mCGLPoVE?FGjRKU{RApEaEy9=yZr-!C*jrs%9!>7!J?AX2*r=glP&nEY>j_a zSl~?Drv$aF{@#o$(jrI~NxL;3#My5x)!H}3C!Tn@f`*uk?wRWkPNpDlq4xX!5>FTV zP;nC0b2n$3A@mA19C5t3f?Qr8`=Smt7}>{j$UYqQ&!x4h4!X&0Ze}sQF4P)&Pra8SJQ0L`1J$bm$U%_b&aL2lJg;K~&6dk#1q07t! z#lx1Bbm8I9%MYl6>UJp%S@Vvi@9xA$xAdKIiH!!0X7ThQ&jTGAM`7idU7kZG2e zr>np%*(djFD)H>mz({2ucUs9J{WvIHX8NJ_F1fKVNJ~6qm~8NKjf+h<4nDTasjDO5 zl-ClL;0sz~b6S$aL;VLHfc`#-Rnkh50L1rN+RwW2vK2Oi_9pQm_OOg}OknXsT-KVM zJvqh+8Sr!grdy5<`SeYROpJ5hV5ZzVi|5-1T~cdw1r`_(%w1#klI-N2hQ|gdQd1yXoL7Qkk{#8L8|b()0Sw zsg8K_n+|D=B128cJg=B+*kn#9E55Q8@L`*ft7!`lG`=@0Jn%>K~asw zf*+b{E_t(T5y39brFMq220KIWZ=tQdF=Xj|A@WP@Z6R62lX4V|+YeKFSBTr?fn{%3 zY0xi8rxhG8BEGzqr=h>)C^Ct;LhA@-Q`6*=QjxZUXvxM0@1>Z;6B2IC%#sZh1|i!| zCW<3i?ob@#5^AqNtvWxqrTvzFNho!*L zXXvZg`g~yH@;+!j*w>s{FXtDGVuhcKx8+kwa$+x~Uwq(QE$vD7>gRXyrU&+EKG&)r zyxgBJiCe#bZ%N6=Pc~sKN_dpsqME%Hs~OQJCUJtUfejPMp09Q8VNB&|SRF~)n^|uv zxt2po%vwERz#^7Pk0=R#VS8kfC`VEO zR0&#jNmjiI~5hc&U*bq`LV-s8To5x+kk89@n)nzyJyKC9+M zGKCNF@h(_(f@qDKT|mJvi7mFaEONJ*2kBEjUSR2ovDtAVk6u8QPb}^H{a$~V;<7&V z1m@S4+oZ_uCJw?~f8%U7Tyz_sovFiPX)hAOGS`9(jxNN??%XDuVBHbBa`atmHe19V zL>%14ou;*pqQdlB9A%KY6X)(WA{1)P;!C&9SsuKrvvUpWU26?;xk%@2(`2r*m2@o9 zkcr2?p)m)RBOyJ9m*AZK9Cj4}rw5?@b<%wk$gVV-zb6*&eOSgA2A5Zf>AX%QBX~QBR1Q@yg{_#~L zh1H1uU7>4{jCNe-&z@^{uDq;-RfNHEfe!uh0$c3l5RHTd3e=yNs1DJ6zei{Gex}N_47?{wv-^ zjuIhD3V#zX)|YMGmBO;?_3=dkvFvcEW-|QL8F#0Iykj zlNyp~>8Y9JaP4c7bVsdl5wi@vSbkci%bGXG1Nu(PDq;;mxlg~PQG>QCm?KI1-{eaH zslhGOfmO@|520`ybiNHiy(1@{7k71Jf7j77sj zjs{~xF6|4(*=Ts-AiM{sVBvG^m{L{ugioC=+GpnFQ_Bh$2Ba%^QOvJ{u1;BPLLTv? zU}{?|7Sfm;Jr1T}1Qa{-_|yp>-gur#udw{br=dj6#hF%uCcv^_!h9$=>mu>y&Q2Mx zXzZ&JHo2tKxPDCpA8XR1HnmGiW+PV^)^tdrmOU1}MUpQ|9m9?#Nh#M$%_mEZ){nUv zXH@yhT1hR#ODAC6$%3lUXap;aO6k3-lt3L^xMWLb`~2E}=LFrHmoqUurO7{f=hpEt z>GP9WI6L{ASed@bbyTvjOeSpBcj1G#)t&N}FONI8*#Ds}$(xjqBtl`scGoGp^4PwN zg)m{nuy&aR7AudsU8sp;B9N%i9Y6l{^he1FA*vQRZX!;Qrq6?Pvnjr$aZHnHka2rW z+ScOV;rnDSQx=Ctx&+dCrisuuWbuhre4!iPu&(u0gASa;lD5%Um3nM4Yn-PiO2e^@?gmgpk@iDmn{Cy?PWtYzSBfs#~7DZe?VeS(hw64AvYD zjHHqt$wCyoHVKVbw{g~L2%U)ZzPcbtI-irQ0Qbo$r0|gAi%-Pu@4m9BDo4ckY^=G9 z-DKI5#c8N5ZTo6iab0pTLYzBF+GjY&9wu#2cm^WLmOW%Eahy6jx$+8*Ov}VGj$oCF zd~_TUbcvAKtUbUZ@k+EfDCOBfB4x^}k`q0cTW9MnCv+4^vy=c$)#R3UcuCy)0@`~V zWtna@PPz#X{uXzFu^`AT`sq0&a>~VcnhD&Kja|Ms*pb_Zw0?iST;r6lwegRbm6gN4 ze935!H8O}yedAO&cM$rDqA$-Cdbg=21P(ZcM2^D`t%pU$qW5rjn`cAel9iC=Tw71) z&H<5`NDsvN!3rdgr6;b!bQ2FqLKi&fPQM9O9Id#IXF*VKSCB>7LaArk1WFZ;uHj(h zoVK{e<@-NjI3C-NKlr0lm;3rdc7pw{$x2-t|7Mc8eA|Y?(Sw&FZ4!t^DP4CC2Qw_1 zIP$;Bz}(O02V-)4BP<;`B#PpDf|LRk#7QklthuvT%Xv3+wwtFh80)M!ZF?EK*=eH; zyKMDc)qNd{(w!ze~9AwER6GYx&*u4c2Tt&Xo>>~fT<4T1rnI<$e zx8FV$w&3s-;{(Fa!PH*Jjj}E-CSqGaT2?vt~44Y)=zA$x=OA;%=JO^rrHEI{7cmpxk zcmjC=@YdOaIHZdeg^#&PvfG{G6;G-!9*Ft8PjbMXTdpnbF4_N^-fR>M>1$MJd>H>S z!Lc6!t|So~WG8p%9&{a?NeqA!#c*CiwhxCZxXE5`m#O$5NYGQ{bQd6fgNqUMQ_1lc zUc&VQDTXe%NP;BPgG$unce1$EqAOU&l?prPFnpc(ZxHO>Elw!jtt#WYsvAm*D+{Bo zaPV%&PrBdP9nX&zC>w3PrXV~UaT1E^Sudd@7h^r~-q6M2<-Gg$s+@9M<4YKW`C__T z@NRQ+eymtP{yB^x)pm zH7rzhYKM*6o9JdRlD-&=^@9EM8d+=VZn+k{@-2Jb^wdZ<6(=yBbYJ-#+^Z8B>F<{$ z=b&jnXR=%}$6*zrgqV#Af_&QgTya*ix>LfTkm0GE}6tDIRboA3S z8LU-7NG3X!5A%^<{E~1IOb3gGawAr7+u@Jr0<(2>Aa5A8CCvsuq_PO+WI8({N9D=4y^@8f=|ae&B^OZiW92iZP_u>7aB&RT zw6P)CB2^yhGl#k?cDk6sVaMiIe91nTMBpCxk%@Rrn|{CBZbMpDhs;-+j*|o^hx%2| zJ^1LG{tzb>r3)QD!R28TljJ_%9ArqlVe8RT3tPi#)oX! zappy@v3&|*8W;w1IS=CQgM!;by#D`^9R@pk+JX=Lx^v;h`M8laQjd#2PlGYdqWPl4 z0&BOWu=VQ^)_elw>@J&XebNgpiZ%=@)z9#`F16MIe0&)sTP<`5+f?9K7wBq{5XD&CkF7AL<}m^oY&!1dvqy(j5z`+Va_I<&w=Iuj4RDWnS-*g*IV zh^E}YfE-Grr}r(6a^o>!GRO-x(HXezB+|0ImdkJ8y{_nP#M*|z64twipAlM=5(?c1 z%^;YWPdxFW;JI1WpJ&}k#J)CUG_vRgUU{ktx53dhb__h?Z8r~akJ0~opP|9WgnRli zMKR#MG!uqTDZ7whD{1Xol}VKas|iwjDf}a3m2B(qU#?zk2Vr#W9}mZ#Xq2f>*wt7f z>PKhpJ1+QSyZhLU31#%4pT*~wCuMZ;m~wYfGk@XNqM2}Yz1gy}xZK%?*4X3@KEUAz z*sRTdF8MYAeqTwNZmHc~VSS0SUDS>;$v%!`D{@?|i?SebPwmoUIB5}M-3fOfo%t#Q zG7_!&a@PYk;iL>-bD>Orh)zYOgA`1k(2*ss4*~7&-OFTM;jn8u5mrpRX5&HZ=dDwf z0Nf8|{_oml7{&m8uEp0}x_aWFP?Gl#s}?5)Ce!5HAr(%%NVlpprissRHKd)VCJS_V zMXAQwYP1-gxsRjVlmNkWfdsbtTRM_Wl9}-|TiSZ=cPV1Z(zjhBw2=3>Pf~h>9~?%Q?PGC1Nr0x){2`_J=s+> z@nT2lk|yg@>n4{K`15h+(CKg7+^Fc9GgjoA0A+ci`w8}xi&LDj0{r;dt${y@w1a#MMwh*I>@bVyk|p0^ zmEg0#Qle#ZPD%8;f~=FS8}SRzy=rSlo{K)Yt7}Rzn;buu_g{|?LDlW4drDS2(IDf6 zI!%pZ#cE-2pN78JxhEed9B|Dt%X2b(J+l7^1`A{sP6@Qg83G-<3x(v6j#^$`W^L$h zU`I8Kxn>6*99||4xK^G!+dM9rjl@ezBhj8%Z*(_jV|u>G$FQZd-q}43R=4>`ic_QCuSuq zFSMCT>q*z0$}viII4EbV4?pwef?=(3j3Q(k8P0*1IY}I*C$fh*c(g2k#$&isEDxQ$+^ZFfv7>sT`onAP`+CqH4N+<15>?M%lgX%J&PmbA6fi{h}jUs=)?=1b>|JTajk01UR$iL-@O6l)^;DcU%{~muY;rBlT&u^{Q2mF1S zFBJKuXkLD$ilUbj_xz4rq`&z48b09P@1OVOe>I=q=ovnZ|KPpx4EzZsZ_ zDDt_hJ$;vF!pqvv>+xgwe0{r8`+H-|SaDWQbkn5Z{o)(_4|=ap{~3J@e~bS7^B4b2 zpZE74JR<#oegEgp^Q(TR6#m{weRwWjZ~pJEndjR!sHOS)tIqb6dbjUi037d4Ct>KPq^B zHJ@M2_s4W(;Q#jbN8ov>KmY!EK3~u0+gTj{{(Aop-p|9s(nS38y`%a-?~C-_NW%~s zx%_*5`?(WX`2OjlJ~y4!d%HXoT>g1~f0B{^D4##d=ht|_$iqM9?@u?+*H!erb^Utp z3f?XM^m2PRgXe{pf6Wx1e<7b=V;oSse~|Z9zQ57n{(1Ab)0L3oF?Y$`w>?7cHp%xN zyG7A@+`fzd`*M4`68Gw5$3Nq1_^6iuU7v~}_YePGk4p2Z_535=r8WM1^MLs?`ai5F BNJIbt diff --git a/lib/spdlog b/lib/spdlog deleted file mode 160000 index 472945b..0000000 --- a/lib/spdlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 472945ba489e3f5684761affc431ae532ab5ed8c diff --git a/lib/spdlog/.clang-tidy b/lib/spdlog/.clang-tidy new file mode 100644 index 0000000..bc50ee7 --- /dev/null +++ b/lib/spdlog/.clang-tidy @@ -0,0 +1,53 @@ +Checks: 'cppcoreguidelines-*, +performance-*, +modernize-*, +google-*, +misc-* +cert-*, +readability-*, +clang-analyzer-*, +-performance-unnecessary-value-param, +-modernize-use-trailing-return-type, +-google-runtime-references, +-misc-non-private-member-variables-in-classes, +-readability-braces-around-statements, +-google-readability-braces-around-statements, +-cppcoreguidelines-avoid-magic-numbers, +-readability-magic-numbers, +-readability-magic-numbers, +-cppcoreguidelines-pro-type-vararg, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-avoid-c-arrays, +-modernize-avoid-c-arrays, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, +-readability-named-parameter, +-cert-env33-c +' + + +WarningsAsErrors: '' +HeaderFilterRegex: '*spdlog/[^f].*' +FormatStyle: none + +CheckOptions: + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + diff --git a/lib/spdlog/CMakeLists.txt b/lib/spdlog/CMakeLists.txt new file mode 100644 index 0000000..c7708eb --- /dev/null +++ b/lib/spdlog/CMakeLists.txt @@ -0,0 +1,413 @@ +# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT) + +cmake_minimum_required(VERSION 3.10...3.21) + +# --------------------------------------------------------------------------------------- +# Start spdlog project +# --------------------------------------------------------------------------------------- +include(cmake/utils.cmake) +include(cmake/ide.cmake) + +spdlog_extract_version() + +project(spdlog VERSION ${SPDLOG_VERSION} LANGUAGES CXX) +message(STATUS "Build spdlog: ${SPDLOG_VERSION}") + +include(GNUInstallDirs) + +# --------------------------------------------------------------------------------------- +# Set default build to release +# --------------------------------------------------------------------------------------- +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + # Set CMAKE_BUILD_TYPE only if this project is top-level + if((DEFINED PROJECT_IS_TOP_LEVEL AND PROJECT_IS_TOP_LEVEL) OR (NOT DEFINED PROJECT_IS_TOP_LEVEL + AND CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)) + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) + endif() +endif() + +# --------------------------------------------------------------------------------------- +# Compiler config +# --------------------------------------------------------------------------------------- +if(SPDLOG_USE_STD_FORMAT) + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +elseif(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +set(CMAKE_CXX_EXTENSIONS OFF) + +if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW") + set(CMAKE_CXX_EXTENSIONS ON) +endif() + +# --------------------------------------------------------------------------------------- +# Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog +# --------------------------------------------------------------------------------------- +# Check if spdlog is being used directly or via add_subdirectory, but allow overriding +if(NOT DEFINED SPDLOG_MASTER_PROJECT) + if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(SPDLOG_MASTER_PROJECT ON) + else() + set(SPDLOG_MASTER_PROJECT OFF) + endif() +endif() + +option(SPDLOG_BUILD_ALL "Build all artifacts" OFF) + +# build shared option +option(SPDLOG_BUILD_SHARED "Build shared library" OFF) + +# precompiled headers option +option(SPDLOG_ENABLE_PCH "Build static or shared library using precompiled header to speed up compilation time" OFF) + +# build position independent code +option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF) + +# debug build postfix +set(SPDLOG_DEBUG_POSTFIX "d" CACHE STRING "Filename postfix for libraries in debug builds") + +# example options +option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT}) +option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF) + +# testing options +option(SPDLOG_BUILD_TESTS "Build tests" OFF) +option(SPDLOG_BUILD_TESTS_HO "Build tests using the header only version" OFF) + +# bench options +option(SPDLOG_BUILD_BENCH "Build benchmarks (Requires https://github.com/google/benchmark.git to be installed)" OFF) + +# sanitizer options +option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF) +option(SPDLOG_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF) +if(SPDLOG_SANITIZE_ADDRESS AND SPDLOG_SANITIZE_THREAD) + message(FATAL_ERROR "SPDLOG_SANITIZE_ADDRESS and SPDLOG_SANITIZE_THREAD are mutually exclusive") +endif() + +# warning options +option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF) + +# install options +option(SPDLOG_SYSTEM_INCLUDES "Include as system headers (skip for clang-tidy)." OFF) +option(SPDLOG_INSTALL "Generate the install target" ${SPDLOG_MASTER_PROJECT}) +option(SPDLOG_USE_STD_FORMAT "Use std::format instead of fmt library." OFF) +option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF) +option(SPDLOG_FMT_EXTERNAL_HO "Use external fmt header-only library instead of bundled" OFF) +option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF) +option(SPDLOG_NO_TZ_OFFSET "Omit %z timezone offset (use on platforms without tm_gmtoff)" OFF) + +if(SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO) + message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive") +endif() + +if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL_HO) + message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive") +endif() + +if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL) + message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive") +endif() + +# misc tweakme options +if(WIN32) + option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF) + option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF) + option(SPDLOG_WCHAR_CONSOLE "Support wchar output to console" OFF) +else() + set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE) + set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE) + set(SPDLOG_WCHAR_CONSOLE OFF CACHE BOOL "non supported option" FORCE) +endif() + +if(MSVC) + option(SPDLOG_MSVC_UTF8 "Enable/disable msvc /utf-8 flag required by fmt lib" ON) +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF) +else() + set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE) +endif() + +option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF) +option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF) +option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF) +option( + SPDLOG_NO_ATOMIC_LEVELS + "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently" + OFF) +option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF) +option(SPDLOG_FWRITE_UNLOCKED "Use the unlocked variant of fwrite. Leave this on unless your libc doesn't have it" ON) + +# clang-tidy +option(SPDLOG_TIDY "run clang-tidy" OFF) + +if(SPDLOG_TIDY) + set(CMAKE_CXX_CLANG_TIDY "clang-tidy") + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + message(STATUS "Enabled clang-tidy") +endif() + +if(SPDLOG_BUILD_PIC) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +find_package(Threads REQUIRED) +message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) +# --------------------------------------------------------------------------------------- +# Static/Shared library +# --------------------------------------------------------------------------------------- +set(SPDLOG_SRCS src/spdlog.cpp src/stdout_sinks.cpp src/color_sinks.cpp src/file_sinks.cpp src/async.cpp src/cfg.cpp) + +if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) + list(APPEND SPDLOG_SRCS src/bundled_fmtlib_format.cpp) +endif() + +if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS) + if(WIN32) + 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) + endif() + add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) + target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + target_compile_options(spdlog PUBLIC $<$,$>>:/wd4251 + /wd4275>) + endif() + if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) + target_compile_definitions(spdlog PRIVATE FMT_LIB_EXPORT PUBLIC FMT_SHARED) + endif() +else() + add_library(spdlog STATIC ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) +endif() + +add_library(spdlog::spdlog ALIAS spdlog) + +set(SPDLOG_INCLUDES_LEVEL "") +if(SPDLOG_SYSTEM_INCLUDES) + set(SPDLOG_INCLUDES_LEVEL "SYSTEM") +endif() + +target_compile_definitions(spdlog PUBLIC SPDLOG_COMPILED_LIB) +target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$" + "$") +target_link_libraries(spdlog PUBLIC Threads::Threads) +spdlog_enable_warnings(spdlog) + +set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION + ${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR}) +set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX ${SPDLOG_DEBUG_POSTFIX}) + +if(COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY) + target_precompile_headers(spdlog PRIVATE ${PROJECT_BINARY_DIR}/spdlog_pch.h) +endif() + +# sanitizer support +if(SPDLOG_SANITIZE_ADDRESS) + spdlog_enable_addr_sanitizer(spdlog) +elseif(SPDLOG_SANITIZE_THREAD) + spdlog_enable_thread_sanitizer(spdlog) +endif() + +# --------------------------------------------------------------------------------------- +# Header only version +# --------------------------------------------------------------------------------------- +add_library(spdlog_header_only INTERFACE) +add_library(spdlog::spdlog_header_only ALIAS spdlog_header_only) + +target_include_directories( + spdlog_header_only ${SPDLOG_INCLUDES_LEVEL} INTERFACE "$" + "$") +target_link_libraries(spdlog_header_only INTERFACE Threads::Threads) + +# --------------------------------------------------------------------------------------- +# Use fmt package if using external fmt +# --------------------------------------------------------------------------------------- +if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO) + if(NOT TARGET fmt::fmt) + find_package(fmt CONFIG REQUIRED) + endif() + target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL) + target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL) + + # use external fmt-header-only + if(SPDLOG_FMT_EXTERNAL_HO) + target_link_libraries(spdlog PUBLIC fmt::fmt-header-only) + target_link_libraries(spdlog_header_only INTERFACE fmt::fmt-header-only) + else() # use external compile fmt + target_link_libraries(spdlog PUBLIC fmt::fmt) + target_link_libraries(spdlog_header_only INTERFACE fmt::fmt) + endif() + + set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config +endif() + +# --------------------------------------------------------------------------------------- +# Check if fwrite_unlocked/_fwrite_nolock is available +# --------------------------------------------------------------------------------------- +if(SPDLOG_FWRITE_UNLOCKED) + include(CheckSymbolExists) + if(WIN32) + check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED) + else() + check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED) + endif() + if(HAVE_FWRITE_UNLOCKED) + target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED) + target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED) + endif() +endif() + +# --------------------------------------------------------------------------------------- +# Add required libraries for Android CMake build +# --------------------------------------------------------------------------------------- +if(ANDROID) + target_link_libraries(spdlog PUBLIC log) + target_link_libraries(spdlog_header_only INTERFACE log) +endif() + +# --------------------------------------------------------------------------------------- +# Misc definitions according to tweak options +# --------------------------------------------------------------------------------------- +set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT}) +set(SPDLOG_UTF8_TO_WCHAR_CONSOLE ${SPDLOG_WCHAR_CONSOLE}) +foreach( + SPDLOG_OPTION + SPDLOG_WCHAR_TO_UTF8_SUPPORT + SPDLOG_UTF8_TO_WCHAR_CONSOLE + SPDLOG_WCHAR_FILENAMES + SPDLOG_NO_EXCEPTIONS + SPDLOG_CLOCK_COARSE + SPDLOG_PREVENT_CHILD_FD + SPDLOG_NO_THREAD_ID + SPDLOG_NO_TLS + SPDLOG_NO_ATOMIC_LEVELS + SPDLOG_DISABLE_DEFAULT_LOGGER + SPDLOG_USE_STD_FORMAT + SPDLOG_NO_TZ_OFFSET) + if(${SPDLOG_OPTION}) + target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION}) + target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION}) + endif() +endforeach() + +if(MSVC) + target_compile_options(spdlog PRIVATE "/Zc:__cplusplus") + target_compile_options(spdlog_header_only INTERFACE "/Zc:__cplusplus") + if(SPDLOG_MSVC_UTF8) + # fmtlib requires the /utf-8 flag when building with msvc. see https://github.com/fmtlib/fmt/pull/4159 on the + # purpose of the additional + # "$<$,$>" + target_compile_options(spdlog PUBLIC $<$,$>:/utf-8>) + target_compile_options(spdlog_header_only + INTERFACE $<$,$>:/utf-8>) + endif() +endif() + +# --------------------------------------------------------------------------------------- +# If exceptions are disabled, disable them in the bundled fmt as well +# --------------------------------------------------------------------------------------- +if(SPDLOG_NO_EXCEPTIONS) + if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) + target_compile_definitions(spdlog PUBLIC FMT_USE_EXCEPTIONS=0) + endif() + if(NOT MSVC) + target_compile_options(spdlog PRIVATE -fno-exceptions) + else() + target_compile_options(spdlog PRIVATE /EHs-c-) + target_compile_definitions(spdlog PRIVATE _HAS_EXCEPTIONS=0) + endif() +endif() +# --------------------------------------------------------------------------------------- +# Build binaries +# --------------------------------------------------------------------------------------- +if(SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_EXAMPLE_HO OR SPDLOG_BUILD_ALL) + message(STATUS "Generating example(s)") + add_subdirectory(example) + spdlog_enable_warnings(example) + if(SPDLOG_BUILD_EXAMPLE_HO) + spdlog_enable_warnings(example_header_only) + endif() +endif() + +if(SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL) + message(STATUS "Generating tests") + enable_testing() + add_subdirectory(tests) +endif() + +if(SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL) + message(STATUS "Generating benchmarks") + add_subdirectory(bench) +endif() + +# --------------------------------------------------------------------------------------- +# Install +# --------------------------------------------------------------------------------------- +if(SPDLOG_INSTALL) + message(STATUS "Generating install") + set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in") + set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake") + set(config_targets_file "spdlogConfigTargets.cmake") + set(version_config_file "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfigVersion.cmake") + set(export_dest_dir "${CMAKE_INSTALL_LIBDIR}/cmake/spdlog") + set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc") + + # --------------------------------------------------------------------------------------- + # Include files + # --------------------------------------------------------------------------------------- + install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "fmt/bundled" EXCLUDE) + install( + TARGETS spdlog spdlog_header_only + EXPORT spdlog + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + + if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) + install(DIRECTORY include/${PROJECT_NAME}/fmt/bundled/ + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/") + endif() + + # --------------------------------------------------------------------------------------- + # Install pkg-config file + # --------------------------------------------------------------------------------------- + if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") + else() + set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") + endif() + if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") + else() + set(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") + endif() + get_target_property(PKG_CONFIG_DEFINES spdlog INTERFACE_COMPILE_DEFINITIONS) + string(REPLACE ";" " -D" PKG_CONFIG_DEFINES "${PKG_CONFIG_DEFINES}") + string(CONCAT PKG_CONFIG_DEFINES "-D" "${PKG_CONFIG_DEFINES}") + configure_file("cmake/${PROJECT_NAME}.pc.in" "${pkg_config}" @ONLY) + install(FILES "${pkg_config}" DESTINATION "${pkgconfig_install_dir}") + + # --------------------------------------------------------------------------------------- + # Install CMake config files + # --------------------------------------------------------------------------------------- + export(TARGETS spdlog spdlog_header_only NAMESPACE spdlog:: + FILE "${CMAKE_CURRENT_BINARY_DIR}/${config_targets_file}") + install(EXPORT spdlog DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file}) + + include(CMakePackageConfigHelpers) + configure_package_config_file("${project_config_in}" "${project_config_out}" INSTALL_DESTINATION ${export_dest_dir}) + + write_basic_package_version_file("${version_config_file}" COMPATIBILITY SameMajorVersion) + install(FILES "${project_config_out}" "${version_config_file}" DESTINATION "${export_dest_dir}") + + # --------------------------------------------------------------------------------------- + # Support creation of installable packages + # --------------------------------------------------------------------------------------- + include(cmake/spdlogCPack.cmake) +endif() diff --git a/lib/spdlog/INSTALL b/lib/spdlog/INSTALL new file mode 100644 index 0000000..4b6fb06 --- /dev/null +++ b/lib/spdlog/INSTALL @@ -0,0 +1,27 @@ +Header Only Version +================================================================== +Just copy the files to your build tree and use a C++11 compiler. +Or use CMake: +``` + add_executable(example_header_only example.cpp) + target_link_libraries(example_header_only spdlog::spdlog_header_only) +``` + +Compiled Library Version +================================================================== +CMake: +``` + add_executable(example example.cpp) + target_link_libraries(example spdlog::spdlog) +``` + +Or copy files src/*.cpp to your build tree and pass the -DSPDLOG_COMPILED_LIB to the compiler. + +Important Information for Compilation: +================================================================== +* If you encounter compilation errors with gcc 4.8.x, please note that gcc 4.8.x does not fully support C++11. In such cases, consider upgrading your compiler or using a different version that fully supports C++11 standards + +Tested on: +gcc 4.8.1 and above +clang 3.5 +Visual Studio 2013 \ No newline at end of file diff --git a/lib/spdlog/LICENSE b/lib/spdlog/LICENSE new file mode 100644 index 0000000..4b1b233 --- /dev/null +++ b/lib/spdlog/LICENSE @@ -0,0 +1,25 @@ +The MIT License (MIT) + +Copyright (c) 2016 - present, Gabi Melman and spdlog contributors. + +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. + +-- NOTE: Third party dependency used by this software -- +This software depends on the fmt lib (MIT License), +and users must comply to its license: https://raw.githubusercontent.com/fmtlib/fmt/master/LICENSE diff --git a/lib/spdlog/README.md b/lib/spdlog/README.md new file mode 100644 index 0000000..a558a6e --- /dev/null +++ b/lib/spdlog/README.md @@ -0,0 +1,553 @@ +# spdlog + + +[![ci](https://github.com/gabime/spdlog/actions/workflows/linux.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/linux.yml)  +[![ci](https://github.com/gabime/spdlog/actions/workflows/windows.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/windows.yml)  +[![ci](https://github.com/gabime/spdlog/actions/workflows/macos.yml/badge.svg)](https://github.com/gabime/spdlog/actions/workflows/macos.yml)  +[![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true&branch=v1.x)](https://ci.appveyor.com/project/gabime/spdlog) [![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest) + +Fast C++ logging library + + +## Install +#### Header-only version +Copy the include [folder](include/spdlog) to your build tree and use a C++11 compiler. + +#### Compiled version (recommended - much faster compile times) +```console +$ git clone https://github.com/gabime/spdlog.git +$ cd spdlog && mkdir build && cd build +$ cmake .. && cmake --build . +``` +see example [CMakeLists.txt](example/CMakeLists.txt) on how to use. + +## Platforms +* Linux, FreeBSD, OpenBSD, Solaris, AIX +* Windows (msvc 2013+, cygwin) +* macOS (clang 3.5+) +* Android + +## Package managers: +* Debian: `sudo apt install libspdlog-dev` +* Homebrew: `brew install spdlog` +* MacPorts: `sudo port install spdlog` +* FreeBSD: `pkg install spdlog` +* Fedora: `dnf install spdlog` +* Gentoo: `emerge dev-libs/spdlog` +* Arch Linux: `pacman -S spdlog` +* openSUSE: `sudo zypper in spdlog-devel` +* ALT Linux: `apt-get install libspdlog-devel` +* vcpkg: `vcpkg install spdlog` +* conan: `conan install --requires=spdlog/[*]` +* conda: `conda install -c conda-forge spdlog` +* build2: ```depends: spdlog ^1.8.2``` + + +## Features +* Very fast (see [benchmarks](#benchmarks) below). +* Headers only or compiled +* Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. +* Asynchronous mode (optional) +* [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting. +* Multi/Single threaded loggers. +* Various log targets: + * Rotating log files. + * Daily log files. + * Console logging (colors supported). + * syslog. + * Windows event log. + * Windows debugger (```OutputDebugString(..)```). + * Log to Qt widgets ([example](#log-to-qt-with-nice-colors)). + * Easily [extendable](https://github.com/gabime/spdlog/wiki/Sinks#implementing-your-own-sink) with custom log targets. +* Log filtering - log levels can be modified at runtime as well as compile time. +* Support for loading log levels from argv or environment var. +* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display them later on demand. + +## Usage samples + +#### Basic usage +```c++ +#include "spdlog/spdlog.h" + +int main() +{ + spdlog::info("Welcome to spdlog!"); + spdlog::error("Some error message with arg: {}", 1); + + spdlog::warn("Easy padding in numbers like {:08d}", 12); + spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + spdlog::info("Support for floats {:03.2f}", 1.23456); + spdlog::info("Positional args are {1} {0}..", "too", "supported"); + spdlog::info("{:<30}", "left aligned"); + + spdlog::set_level(spdlog::level::debug); // Set *global* log level to debug + spdlog::debug("This message should be displayed.."); + + // change log pattern + spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v"); + + // Compile time log levels + // Note that this does not change the current log level, it will only + // remove (depending on SPDLOG_ACTIVE_LEVEL) the call on the release code. + SPDLOG_TRACE("Some trace message with param {}", 42); + SPDLOG_DEBUG("Some debug message"); +} + +``` +--- +#### Create stdout/stderr logger object +```c++ +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" +void stdout_example() +{ + // create a color multi-threaded logger + auto console = spdlog::stdout_color_mt("console"); + auto err_logger = spdlog::stderr_color_mt("stderr"); + spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); +} +``` + +--- +#### Basic file logger +```c++ +#include "spdlog/sinks/basic_file_sink.h" +void basic_logfile_example() +{ + try + { + auto logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); + } + catch (const spdlog::spdlog_ex &ex) + { + std::cout << "Log init failed: " << ex.what() << std::endl; + } +} +``` +--- +#### Rotating files +```c++ +#include "spdlog/sinks/rotating_file_sink.h" +void rotating_example() +{ + // Create a file rotating logger with 5 MB size max and 3 rotated files + auto max_size = 1048576 * 5; + auto max_files = 3; + auto logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", max_size, max_files); +} +``` + +--- +#### Daily files +```c++ + +#include "spdlog/sinks/daily_file_sink.h" +void daily_example() +{ + // Create a daily logger - a new file is created every day at 2:30 am + auto logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); +} + +``` + +--- +#### Backtrace support +```c++ +// Debug messages can be stored in a ring buffer instead of being logged immediately. +// This is useful to display debug logs only when needed (e.g. when an error happens). +// When needed, call dump_backtrace() to dump them to your log. + +spdlog::enable_backtrace(32); // Store the latest 32 messages in a buffer. +// or my_logger->enable_backtrace(32).. +for(int i = 0; i < 100; i++) +{ + spdlog::debug("Backtrace message {}", i); // not logged yet.. +} +// e.g. if some error happened: +spdlog::dump_backtrace(); // log them now! show the last 32 messages +// or my_logger->dump_backtrace(32).. +``` + +--- +#### Periodic flush +```c++ +// periodically flush all *registered* loggers every 3 seconds: +// warning: only use if all your loggers are thread-safe ("_mt" loggers) +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 +```c++ +// many types of std::container types can be used. +// ranges are supported too. +// format flags: +// {:X} - print in uppercase. +// {:s} - don't separate each byte with space. +// {:p} - don't print the position on each line start. +// {:n} - don't split the output into lines. +// {:a} - show ASCII if :n is not set. + +#include "spdlog/fmt/bin_to_hex.h" + +void binary_example() +{ + auto console = spdlog::get("console"); + std::array buf; + console->info("Binary example: {}", spdlog::to_hex(buf)); + console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); + // more examples: + // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); +} + +``` + +--- +#### Logger with multi sinks - each with a different format and log level +```c++ + +// create a logger with 2 targets, with different log levels and formats. +// The console will show only warnings or errors, while the file will log all. +void multi_sink_example() +{ + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::warn); + console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); + + auto file_sink = std::make_shared("logs/multisink.txt", true); + file_sink->set_level(spdlog::level::trace); + + spdlog::logger logger("multi_sink", {console_sink, file_sink}); + logger.set_level(spdlog::level::debug); + logger.warn("this should appear in both console and file"); + logger.info("this message should not appear in the console, only in the file"); +} +``` + +--- +#### Register several loggers - change global level +```c++ + +// Creation of loggers. Set levels to all registered loggers. +void set_level_example() +{ + auto logger1 = spdlog::basic_logger_mt("logger1", "logs/logger1.txt"); + auto logger2 = spdlog::basic_logger_mt("logger2", "logs/logger2.txt"); + + spdlog::set_default_logger(logger2); + spdlog::default_logger()->set_level(spdlog::level::trace); // set level for the default logger (logger2) to trace + + spdlog::trace("trace message to the logger2 (specified as default)"); + + spdlog::set_level(spdlog::level::off) // (sic!) set level for *all* registered loggers to off (disable) + + logger1.warn("warn message will not appear because the level set to off"); + logger2.warn("warn message will not appear because the level set to off"); + spdlog::warn("warn message will not appear because the level set to off"); +} +``` + +--- +#### User-defined callbacks about log events +```c++ + +// create a logger with a lambda function callback, the callback will be called +// each time something is logged to the logger +void callback_example() +{ + auto callback_sink = std::make_shared([](const spdlog::details::log_msg &msg) { + // for example you can be notified by sending an email to yourself + }); + callback_sink->set_level(spdlog::level::err); + + auto console_sink = std::make_shared(); + spdlog::logger logger("custom_callback_logger", {console_sink, callback_sink}); + + logger.info("some info log"); + logger.error("critical issue"); // will notify you +} +``` + +--- +#### Asynchronous logging +```c++ +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +void async_example() +{ + // default thread pool settings can be modified *before* creating the async logger: + // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. + auto async_file = spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); + // alternatively: + // auto async_file = spdlog::create_async("async_file_logger", "logs/async_log.txt"); +} + +``` + +--- +#### Asynchronous logger with multi sinks +```c++ +#include "spdlog/async.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/rotating_file_sink.h" + +void multi_sink_example2() +{ + spdlog::init_thread_pool(8192, 1); + auto stdout_sink = std::make_shared(); + auto rotating_sink = std::make_shared("mylog.txt", 1024*1024*10, 3); + std::vector sinks {stdout_sink, rotating_sink}; + auto logger = std::make_shared("loggername", sinks.begin(), sinks.end(), spdlog::thread_pool(), spdlog::async_overflow_policy::block); + spdlog::register_logger(logger); +} +``` + +--- +#### User-defined types +```c++ +template<> +struct fmt::formatter : fmt::formatter +{ + auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) + { + return fmt::format_to(ctx.out(), "[my_type i={}]", my.i); + } +}; + +void user_defined_example() +{ + spdlog::info("user defined type: {}", my_type(14)); +} + +``` + +--- +#### User-defined flags in the log pattern +```c++ +// Log patterns can contain custom flags. +// the following example will add new flag '%*' - which will be bound to a instance. +#include "spdlog/pattern_formatter.h" +class my_formatter_flag : public spdlog::custom_flag_formatter +{ +public: + void format(const spdlog::details::log_msg &, const std::tm &, spdlog::memory_buf_t &dest) override + { + std::string some_txt = "custom-flag"; + dest.append(some_txt.data(), some_txt.data() + some_txt.size()); + } + + std::unique_ptr clone() const override + { + return spdlog::details::make_unique(); + } +}; + +void custom_flags_example() +{ + auto formatter = std::make_unique(); + formatter->add_flag('*').set_pattern("[%n] [%*] [%^%l%$] %v"); + spdlog::set_formatter(std::move(formatter)); +} + +``` + +--- +#### Custom error handler +```c++ +void err_handler_example() +{ + // can be set globally or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** LOGGER ERROR ***: {}", msg); }); + spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3); +} + +``` + +--- +#### syslog +```c++ +#include "spdlog/sinks/syslog_sink.h" +void syslog_example() +{ + std::string ident = "spdlog-example"; + auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +} +``` +--- +#### Android example +```c++ +#include "spdlog/sinks/android_sink.h" +void android_example() +{ + std::string tag = "spdlog-android"; + auto android_logger = spdlog::android_logger_mt("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +} +``` + +--- +#### Load log levels from the env variable or argv + +```c++ +#include "spdlog/cfg/env.h" +int main (int argc, char *argv[]) +{ + spdlog::cfg::load_env_levels(); + // or specify the env variable name: + // MYAPP_LEVEL=info,mylogger=trace && ./example + // spdlog::cfg::load_env_levels("MYAPP_LEVEL"); + // or from the command line: + // ./example SPDLOG_LEVEL=info,mylogger=trace + // #include "spdlog/cfg/argv.h" // for loading levels from argv + // spdlog::cfg::load_argv_levels(argc, argv); +} +``` +So then you can: + +```console +$ export SPDLOG_LEVEL=info,mylogger=trace +$ ./example +``` + + +--- +#### Log file open/close event handlers +```c++ +// You can get callbacks from spdlog before/after a log file has been opened or closed. +// This is useful for cleanup procedures or for adding something to the start/end of the log file. +void file_events_example() +{ + // pass the spdlog::file_event_handlers to file sinks for open/close log file notifications + spdlog::file_event_handlers handlers; + handlers.before_open = [](spdlog::filename_t filename) { spdlog::info("Before opening {}", filename); }; + handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("After opening\n", fstream); }; + handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("Before closing\n", fstream); }; + handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); }; + auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers); +} +``` + +--- +#### Replace the Default Logger +```c++ +void replace_default_logger_example() +{ + auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true); + spdlog::set_default_logger(new_logger); + spdlog::info("new logger log message"); +} +``` + +--- +#### Log to Qt with nice colors +```c++ +#include "spdlog/spdlog.h" +#include "spdlog/sinks/qt_sinks.h" +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) +{ + setMinimumSize(640, 480); + auto log_widget = new QTextEdit(this); + setCentralWidget(log_widget); + int max_lines = 500; // keep the text widget to max 500 lines. remove old lines if needed. + auto logger = spdlog::qt_color_logger_mt("qt_logger", log_widget, max_lines); + logger->info("Some info message"); +} +``` +--- + +#### Mapped Diagnostic Context +```c++ +// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread local storage. +// Each thread maintains its own MDC, which loggers use to append diagnostic information to log outputs. +// Note: it is not supported in asynchronous mode due to its reliance on thread-local storage. +#include "spdlog/mdc.h" +void mdc_example() +{ + spdlog::mdc::put("key1", "value1"); + spdlog::mdc::put("key2", "value2"); + // if not using the default format, use the %& formatter to print mdc data + // spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [%&] %v"); +} +``` +--- +## Benchmarks + +Below are some [benchmarks](bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz + +#### Synchronous mode +``` +[info] ************************************************************** +[info] Single thread, 1,000,000 iterations +[info] ************************************************************** +[info] basic_st Elapsed: 0.17 secs 5,777,626/sec +[info] rotating_st Elapsed: 0.18 secs 5,475,894/sec +[info] daily_st Elapsed: 0.20 secs 5,062,659/sec +[info] empty_logger Elapsed: 0.07 secs 14,127,300/sec +[info] ************************************************************** +[info] C-string (400 bytes). Single thread, 1,000,000 iterations +[info] ************************************************************** +[info] basic_st Elapsed: 0.41 secs 2,412,483/sec +[info] rotating_st Elapsed: 0.72 secs 1,389,196/sec +[info] daily_st Elapsed: 0.42 secs 2,393,298/sec +[info] null_st Elapsed: 0.04 secs 27,446,957/sec +[info] ************************************************************** +[info] 10 threads, competing over the same logger object, 1,000,000 iterations +[info] ************************************************************** +[info] basic_mt Elapsed: 0.60 secs 1,659,613/sec +[info] rotating_mt Elapsed: 0.62 secs 1,612,493/sec +[info] daily_mt Elapsed: 0.61 secs 1,638,305/sec +[info] null_mt Elapsed: 0.16 secs 6,272,758/sec +``` +#### Asynchronous mode +``` +[info] ------------------------------------------------- +[info] Messages : 1,000,000 +[info] Threads : 10 +[info] Queue : 8,192 slots +[info] Queue memory : 8,192 x 272 = 2,176 KB +[info] ------------------------------------------------- +[info] +[info] ********************************* +[info] Queue Overflow Policy: block +[info] ********************************* +[info] Elapsed: 1.70784 secs 585,535/sec +[info] Elapsed: 1.69805 secs 588,910/sec +[info] Elapsed: 1.7026 secs 587,337/sec +[info] +[info] ********************************* +[info] Queue Overflow Policy: overrun +[info] ********************************* +[info] Elapsed: 0.372816 secs 2,682,285/sec +[info] Elapsed: 0.379758 secs 2,633,255/sec +[info] Elapsed: 0.373532 secs 2,677,147/sec + +``` + +## Documentation + +Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki) pages. + +--- + +### Powered by + + JetBrains logo + diff --git a/lib/spdlog/appveyor.yml b/lib/spdlog/appveyor.yml new file mode 100644 index 0000000..f2757f3 --- /dev/null +++ b/lib/spdlog/appveyor.yml @@ -0,0 +1,89 @@ +version: 1.0.{build} +image: Visual Studio 2017 +environment: + matrix: + - GENERATOR: '"Visual Studio 15 2017 Win64"' + BUILD_TYPE: Debug + BUILD_SHARED: 'OFF' + FATAL_ERRORS: 'OFF' + WCHAR: 'ON' + WCHAR_FILES: 'OFF' + BUILD_EXAMPLE: 'ON' + USE_STD_FORMAT: 'OFF' + CXX_STANDARD: 11 + - GENERATOR: '"Visual Studio 15 2017 Win64"' + BUILD_TYPE: Release + BUILD_SHARED: 'OFF' + FATAL_ERRORS: 'OFF' + WCHAR: 'OFF' + WCHAR_FILES: 'OFF' + BUILD_EXAMPLE: 'ON' + USE_STD_FORMAT: 'OFF' + CXX_STANDARD: 11 + - GENERATOR: '"Visual Studio 15 2017 Win64"' + BUILD_TYPE: Release + BUILD_SHARED: 'ON' + FATAL_ERRORS: 'OFF' + WCHAR: 'OFF' + WCHAR_FILES: 'OFF' + BUILD_EXAMPLE: 'ON' + USE_STD_FORMAT: 'OFF' + CXX_STANDARD: 11 + - GENERATOR: '"Visual Studio 15 2017 Win64"' + BUILD_TYPE: Release + BUILD_SHARED: 'ON' + FATAL_ERRORS: 'OFF' + WCHAR: 'ON' + WCHAR_FILES: 'ON' + BUILD_EXAMPLE: 'OFF' + USE_STD_FORMAT: 'OFF' + CXX_STANDARD: 11 + - GENERATOR: '"Visual Studio 16 2019" -A x64' + BUILD_TYPE: Release + BUILD_SHARED: 'ON' + FATAL_ERRORS: 'ON' + WCHAR: 'OFF' + WCHAR_FILES: 'OFF' + BUILD_EXAMPLE: 'OFF' + USE_STD_FORMAT: 'OFF' + CXX_STANDARD: 17 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - GENERATOR: '"Visual Studio 17 2022" -A x64' + BUILD_TYPE: Release + BUILD_SHARED: 'ON' + FATAL_ERRORS: 'ON' + WCHAR: 'OFF' + WCHAR_FILES: 'OFF' + BUILD_EXAMPLE: 'OFF' + USE_STD_FORMAT: 'ON' + CXX_STANDARD: 20 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + - GENERATOR: '"Visual Studio 17 2022" -A x64' + BUILD_TYPE: Release + BUILD_SHARED: 'ON' + FATAL_ERRORS: 'ON' + WCHAR: 'ON' + WCHAR_FILES: 'ON' + BUILD_EXAMPLE: 'OFF' + USE_STD_FORMAT: 'ON' + CXX_STANDARD: 20 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 +build_script: + - cmd: >- + set + + mkdir build + + cd build + + set PATH=%PATH%;C:\Program Files\Git\usr\bin + + 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=%FATAL_ERRORS% -D SPDLOG_USE_STD_FORMAT=%USE_STD_FORMAT% -D CMAKE_CXX_STANDARD=%CXX_STANDARD% .. + + cmake --build . --config %BUILD_TYPE% + +before_test: + - set PATH=%PATH%;C:\projects\spdlog\build\_deps\catch2-build\src\%BUILD_TYPE%;C:\projects\spdlog\build\%BUILD_TYPE% + +test_script: + - C:\projects\spdlog\build\tests\%BUILD_TYPE%\spdlog-utests.exe diff --git a/lib/spdlog/bench/CMakeLists.txt b/lib/spdlog/bench/CMakeLists.txt new file mode 100644 index 0000000..3806b24 --- /dev/null +++ b/lib/spdlog/bench/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT) + +cmake_minimum_required(VERSION 3.11) +project(spdlog_bench CXX) + +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog CONFIG REQUIRED) +endif() + +find_package(Threads REQUIRED) +find_package(benchmark CONFIG) +if(NOT benchmark_FOUND) + message(STATUS "Using CMake Version ${CMAKE_VERSION}") + # User can fetch googlebenchmark + message(STATUS "Downloading GoogleBenchmark") + include(FetchContent) + + # disable tests + set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "") + # Do not build and run googlebenchmark tests + FetchContent_Declare(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.6.0) + FetchContent_MakeAvailable(googlebenchmark) +endif() + +add_executable(bench bench.cpp) +spdlog_enable_warnings(bench) +target_link_libraries(bench PRIVATE spdlog::spdlog) + +add_executable(async_bench async_bench.cpp) +target_link_libraries(async_bench PRIVATE spdlog::spdlog) + +add_executable(latency latency.cpp) +target_link_libraries(latency PRIVATE benchmark::benchmark spdlog::spdlog) + +add_executable(formatter-bench formatter-bench.cpp) +target_link_libraries(formatter-bench PRIVATE benchmark::benchmark spdlog::spdlog) diff --git a/lib/spdlog/bench/async_bench.cpp b/lib/spdlog/bench/async_bench.cpp new file mode 100644 index 0000000..13f4c51 --- /dev/null +++ b/lib/spdlog/bench/async_bench.cpp @@ -0,0 +1,168 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include "spdlog/spdlog.h" +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" + +#if defined(SPDLOG_USE_STD_FORMAT) +#include +#elif defined(SPDLOG_FMT_EXTERNAL) +#include +#else +#include "spdlog/fmt/bundled/format.h" +#endif + +#include "utils.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; +using namespace spdlog; +using namespace spdlog::sinks; +using namespace utils; + +void bench_mt(int howmany, std::shared_ptr log, int thread_count); + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) // disable fopen warning under msvc +#endif // _MSC_VER + +int count_lines(const char *filename) { + int counter = 0; + auto *infile = fopen(filename, "r"); + int ch; + while (EOF != (ch = getc(infile))) { + if ('\n' == ch) counter++; + } + fclose(infile); + + return counter; +} + +void verify_file(const char *filename, int expected_count) { + spdlog::info("Verifying {} to contain {} line..", filename, expected_count); + auto count = count_lines(filename); + if (count != expected_count) { + spdlog::error("Test failed. {} has {} lines instead of {}", filename, count, + expected_count); + exit(1); + } + spdlog::info("Line count OK ({})\n", count); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +int main(int argc, char *argv[]) { + int howmany = 1000000; + int queue_size = std::min(howmany + 2, 8192); + int threads = 10; + int iters = 3; + + try { + spdlog::set_pattern("[%^%l%$] %v"); + if (argc == 1) { + spdlog::info("Usage: {} ", argv[0]); + return 0; + } + + if (argc > 1) howmany = atoi(argv[1]); + if (argc > 2) threads = atoi(argv[2]); + if (argc > 3) { + queue_size = atoi(argv[3]); + if (queue_size > 500000) { + spdlog::error("Max queue size allowed: 500,000"); + exit(1); + } + } + + if (argc > 4) iters = atoi(argv[4]); + + auto slot_size = sizeof(spdlog::details::async_msg); + spdlog::info("-------------------------------------------------"); + spdlog::info("Messages : {:L}", howmany); + spdlog::info("Threads : {:L}", threads); + spdlog::info("Queue : {:L} slots", queue_size); + spdlog::info("Queue memory : {:L} x {:L} = {:L} KB ", queue_size, slot_size, + (queue_size * slot_size) / 1024); + spdlog::info("Total iters : {:L}", iters); + spdlog::info("-------------------------------------------------"); + + const char *filename = "logs/basic_async.log"; + spdlog::info(""); + spdlog::info("*********************************"); + spdlog::info("Queue Overflow Policy: block"); + spdlog::info("*********************************"); + for (int i = 0; i < iters; i++) { + auto tp = std::make_shared(queue_size, 1); + auto file_sink = std::make_shared(filename, true); + auto logger = std::make_shared( + "async_logger", std::move(file_sink), std::move(tp), async_overflow_policy::block); + bench_mt(howmany, std::move(logger), threads); + // verify_file(filename, howmany); + } + + spdlog::info(""); + spdlog::info("*********************************"); + spdlog::info("Queue Overflow Policy: overrun"); + spdlog::info("*********************************"); + // do same test but discard oldest if queue is full instead of blocking + filename = "logs/basic_async-overrun.log"; + for (int i = 0; i < iters; i++) { + auto tp = std::make_shared(queue_size, 1); + auto file_sink = std::make_shared(filename, true); + auto logger = + std::make_shared("async_logger", std::move(file_sink), std::move(tp), + async_overflow_policy::overrun_oldest); + bench_mt(howmany, std::move(logger), threads); + } + spdlog::shutdown(); + } catch (std::exception &ex) { + std::cerr << "Error: " << ex.what() << std::endl; + perror("Last error"); + return 1; + } + return 0; +} + +void thread_fun(std::shared_ptr logger, int howmany) { + for (int i = 0; i < howmany; i++) { + logger->info("Hello logger: msg number {}", i); + } +} + +void bench_mt(int howmany, std::shared_ptr logger, int thread_count) { + using std::chrono::high_resolution_clock; + vector threads; + auto start = high_resolution_clock::now(); + + int msgs_per_thread = howmany / thread_count; + int msgs_per_thread_mod = howmany % thread_count; + for (int t = 0; t < thread_count; ++t) { + if (t == 0 && msgs_per_thread_mod) + threads.push_back( + std::thread(thread_fun, logger, msgs_per_thread + msgs_per_thread_mod)); + else + threads.push_back(std::thread(thread_fun, logger, msgs_per_thread)); + } + + for (auto &t : threads) { + t.join(); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + spdlog::info("Elapsed: {} secs\t {:L}/sec", delta_d, int(howmany / delta_d)); +} diff --git a/lib/spdlog/bench/bench.cpp b/lib/spdlog/bench/bench.cpp new file mode 100644 index 0000000..8f859d6 --- /dev/null +++ b/lib/spdlog/bench/bench.cpp @@ -0,0 +1,246 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// bench.cpp : spdlog benchmarks +// +#include "spdlog/spdlog.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" + +#if defined(SPDLOG_USE_STD_FORMAT) +#include +#elif defined(SPDLOG_FMT_EXTERNAL) +#include +#else +#include "spdlog/fmt/bundled/format.h" +#endif + +#include "utils.h" +#include +#include // EXIT_FAILURE +#include +#include +#include + +void bench(int howmany, std::shared_ptr log); +void bench_mt(int howmany, std::shared_ptr log, size_t thread_count); + +// void bench_default_api(int howmany, std::shared_ptr log); +// void bench_c_string(int howmany, std::shared_ptr log); + +static const size_t file_size = 30 * 1024 * 1024; +static const size_t rotating_files = 5; +static const int max_threads = 1000; + +void bench_threaded_logging(size_t threads, int iters) { + spdlog::info("**************************************************************"); + spdlog::info(spdlog::fmt_lib::format( + std::locale("en_US.UTF-8"), "Multi threaded: {:L} threads, {:L} messages", threads, iters)); + spdlog::info("**************************************************************"); + + auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); + bench_mt(iters, std::move(basic_mt), threads); + auto basic_mt_tracing = + spdlog::basic_logger_mt("basic_mt/backtrace-on", "logs/basic_mt.log", true); + basic_mt_tracing->enable_backtrace(32); + bench_mt(iters, std::move(basic_mt_tracing), threads); + + spdlog::info(""); + auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, + rotating_files); + bench_mt(iters, std::move(rotating_mt), threads); + auto rotating_mt_tracing = spdlog::rotating_logger_mt( + "rotating_mt/backtrace-on", "logs/rotating_mt.log", file_size, rotating_files); + rotating_mt_tracing->enable_backtrace(32); + bench_mt(iters, std::move(rotating_mt_tracing), threads); + + spdlog::info(""); + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); + bench_mt(iters, std::move(daily_mt), threads); + auto daily_mt_tracing = spdlog::daily_logger_mt("daily_mt/backtrace-on", "logs/daily_mt.log"); + daily_mt_tracing->enable_backtrace(32); + bench_mt(iters, std::move(daily_mt_tracing), threads); + + spdlog::info(""); + auto empty_logger = std::make_shared("level-off"); + empty_logger->set_level(spdlog::level::off); + bench(iters, empty_logger); + auto empty_logger_tracing = std::make_shared("level-off/backtrace-on"); + empty_logger_tracing->set_level(spdlog::level::off); + empty_logger_tracing->enable_backtrace(32); + bench(iters, empty_logger_tracing); +} + +void bench_single_threaded(int iters) { + spdlog::info("**************************************************************"); + spdlog::info( + spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), "Single threaded: {} messages", iters)); + spdlog::info("**************************************************************"); + + auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true); + bench(iters, std::move(basic_st)); + + auto basic_st_tracing = + spdlog::basic_logger_st("basic_st/backtrace-on", "logs/basic_st.log", true); + bench(iters, std::move(basic_st_tracing)); + + spdlog::info(""); + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, + rotating_files); + bench(iters, std::move(rotating_st)); + auto rotating_st_tracing = spdlog::rotating_logger_st( + "rotating_st/backtrace-on", "logs/rotating_st.log", file_size, rotating_files); + rotating_st_tracing->enable_backtrace(32); + bench(iters, std::move(rotating_st_tracing)); + + spdlog::info(""); + auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); + bench(iters, std::move(daily_st)); + auto daily_st_tracing = spdlog::daily_logger_st("daily_st/backtrace-on", "logs/daily_st.log"); + daily_st_tracing->enable_backtrace(32); + bench(iters, std::move(daily_st_tracing)); + + spdlog::info(""); + auto empty_logger = std::make_shared("level-off"); + empty_logger->set_level(spdlog::level::off); + bench(iters, empty_logger); + + auto empty_logger_tracing = std::make_shared("level-off/backtrace-on"); + empty_logger_tracing->set_level(spdlog::level::off); + empty_logger_tracing->enable_backtrace(32); + bench(iters, empty_logger_tracing); +} + +int main(int argc, char *argv[]) { + spdlog::set_automatic_registration(false); + spdlog::default_logger()->set_pattern("[%^%l%$] %v"); + int iters = 250000; + size_t threads = 4; + try { + if (argc > 1) { + iters = std::stoi(argv[1]); + } + if (argc > 2) { + threads = std::stoul(argv[2]); + } + + if (threads > max_threads) { + throw std::runtime_error( + spdlog::fmt_lib::format("Number of threads exceeds maximum({})", max_threads)); + } + + bench_single_threaded(iters); + bench_threaded_logging(1, iters); + bench_threaded_logging(threads, iters); + } catch (std::exception &ex) { + spdlog::error(ex.what()); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +void bench(int howmany, std::shared_ptr log) { + using std::chrono::duration; + using std::chrono::duration_cast; + using std::chrono::high_resolution_clock; + + auto start = high_resolution_clock::now(); + for (auto i = 0; i < howmany; ++i) { + log->info("Hello logger: msg number {}", i); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + + spdlog::info(spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), + "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), + delta_d, size_t(howmany / delta_d))); + spdlog::drop(log->name()); +} + +void bench_mt(int howmany, std::shared_ptr log, size_t thread_count) { + using std::chrono::duration; + using std::chrono::duration_cast; + using std::chrono::high_resolution_clock; + + std::vector threads; + threads.reserve(thread_count); + auto start = high_resolution_clock::now(); + for (size_t t = 0; t < thread_count; ++t) { + threads.emplace_back([&]() { + for (int j = 0; j < howmany / static_cast(thread_count); j++) { + log->info("Hello logger: msg number {}", j); + } + }); + } + + for (auto &t : threads) { + t.join(); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + spdlog::info(spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), + "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), + delta_d, size_t(howmany / delta_d))); + spdlog::drop(log->name()); +} + +/* +void bench_default_api(int howmany, std::shared_ptr log) +{ + using std::chrono::high_resolution_clock; + using std::chrono::duration; + using std::chrono::duration_cast; + + auto orig_default = spdlog::default_logger(); + spdlog::set_default_logger(log); + auto start = high_resolution_clock::now(); + for (auto i = 0; i < howmany; ++i) + { + spdlog::info("Hello logger: msg number {}", i); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + spdlog::drop(log->name()); + spdlog::set_default_logger(std::move(orig_default)); + 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 log) +{ + using std::chrono::high_resolution_clock; + using std::chrono::duration; + using std::chrono::duration_cast; + + const char *msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra +metus cursus " "lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus +volutpat mi, eu consequat sem " "libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam +non dapibus eros. Donec fringilla dui sed " "augue pretium, nec scelerisque est maximus. Nullam +convallis, sem nec blandit maximus, nisi turpis ornare " "nisl, sit amet volutpat neque massa eu +odio. Maecenas malesuada quam ex, posuere congue nibh turpis duis."; + + auto orig_default = spdlog::default_logger(); + spdlog::set_default_logger(log); + auto start = high_resolution_clock::now(); + for (auto i = 0; i < howmany; ++i) + { + spdlog::log(spdlog::level::info, msg); + } + + auto delta = high_resolution_clock::now() - start; + auto delta_d = duration_cast>(delta).count(); + spdlog::drop(log->name()); + spdlog::set_default_logger(std::move(orig_default)); + spdlog::info("{:<30} Elapsed: {:0.2f} secs {:>16}/sec", log->name(), delta_d, int(howmany / +delta_d)); +} + +*/ diff --git a/lib/spdlog/bench/formatter-bench.cpp b/lib/spdlog/bench/formatter-bench.cpp new file mode 100644 index 0000000..375a154 --- /dev/null +++ b/lib/spdlog/bench/formatter-bench.cpp @@ -0,0 +1,71 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "benchmark/benchmark.h" + +#include "spdlog/spdlog.h" +#include "spdlog/pattern_formatter.h" + +void bench_formatter(benchmark::State &state, std::string pattern) { + auto formatter = spdlog::details::make_unique(pattern); + spdlog::memory_buf_t dest; + std::string logger_name = "logger-name"; + const char *text = + "Hello. This is some message with length of 80 "; + + spdlog::source_loc source_loc{"a/b/c/d/myfile.cpp", 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, logger_name, spdlog::level::info, text); + + for (auto _ : state) { + dest.clear(); + formatter->format(msg, dest); + benchmark::DoNotOptimize(dest); + } +} + +void bench_formatters() { + // basic patterns(single flag) + std::string all_flags = "+vtPnlLaAbBcCYDmdHIMSefFprRTXzEisg@luioO%"; + std::vector basic_patterns; + for (auto &flag : all_flags) { + auto pattern = std::string("%") + flag; + benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern); + + // pattern = std::string("%16") + flag; + // benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern); + // + // // bench center padding + // pattern = std::string("%=16") + flag; + // benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern); + } + + // complex patterns + std::vector patterns = { + "[%D %X] [%l] [%n] %v", + "[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v", + "[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%t] %v", + }; + for (auto &pattern : patterns) { + benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern) + ->Iterations(2500000); + } +} + +int main(int argc, char *argv[]) { + spdlog::set_pattern("[%^%l%$] %v"); + if (argc != 2) { + spdlog::error("Usage: {} (or \"all\" to bench all)", argv[0]); + exit(1); + } + + std::string pattern = argv[1]; + if (pattern == "all") { + bench_formatters(); + } else { + benchmark::RegisterBenchmark(pattern.c_str(), &bench_formatter, pattern); + } + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/lib/spdlog/bench/latency.cpp b/lib/spdlog/bench/latency.cpp new file mode 100644 index 0000000..1999317 --- /dev/null +++ b/lib/spdlog/bench/latency.cpp @@ -0,0 +1,220 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// +// latency.cpp : spdlog latency benchmarks +// + +#include "benchmark/benchmark.h" + +#include "spdlog/spdlog.h" +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" + +void bench_c_string(benchmark::State &state, std::shared_ptr logger) { + const char *msg = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus " + "lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, " + "eu consequat sem " + "libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec " + "fringilla dui sed " + "augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, " + "nisi turpis ornare " + "nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue " + "nibh turpis duis."; + + for (auto _ : state) { + logger->info(msg); + } +} + +void bench_logger(benchmark::State &state, std::shared_ptr logger) { + int i = 0; + for (auto _ : state) { + logger->info("Hello logger: msg number {}...............", ++i); + } +} +void bench_global_logger(benchmark::State &state, std::shared_ptr logger) { + spdlog::set_default_logger(std::move(logger)); + int i = 0; + for (auto _ : state) { + spdlog::info("Hello logger: msg number {}...............", ++i); + } +} + +void bench_disabled_macro(benchmark::State &state, std::shared_ptr logger) { + int i = 0; + benchmark::DoNotOptimize(i); // prevent unused warnings + benchmark::DoNotOptimize(logger); // prevent unused warnings + for (auto _ : state) { + SPDLOG_LOGGER_DEBUG(logger, "Hello logger: msg number {}...............", i++); + } +} + +void bench_disabled_macro_global_logger(benchmark::State &state, + std::shared_ptr logger) { + spdlog::set_default_logger(std::move(logger)); + int i = 0; + benchmark::DoNotOptimize(i); // prevent unused warnings + benchmark::DoNotOptimize(logger); // prevent unused warnings + for (auto _ : state) { + SPDLOG_DEBUG("Hello logger: msg number {}...............", i++); + } +} + +#ifdef __linux__ +void bench_dev_null() { + auto dev_null_st = spdlog::basic_logger_st("/dev/null_st", "/dev/null"); + benchmark::RegisterBenchmark("/dev/null_st", bench_logger, std::move(dev_null_st)) + ->UseRealTime(); + spdlog::drop("/dev/null_st"); + + auto dev_null_mt = spdlog::basic_logger_mt("/dev/null_mt", "/dev/null"); + benchmark::RegisterBenchmark("/dev/null_mt", bench_logger, std::move(dev_null_mt)) + ->UseRealTime(); + spdlog::drop("/dev/null_mt"); +} +#endif // __linux__ + +int main(int argc, char *argv[]) { + using spdlog::sinks::null_sink_mt; + using spdlog::sinks::null_sink_st; + + size_t file_size = 30 * 1024 * 1024; + size_t rotating_files = 5; + int n_threads = benchmark::CPUInfo::Get().num_cpus; + + auto full_bench = argc > 1 && std::string(argv[1]) == "full"; + + // disabled loggers + auto disabled_logger = + std::make_shared("bench", std::make_shared()); + disabled_logger->set_level(spdlog::level::off); + benchmark::RegisterBenchmark("disabled-at-compile-time", bench_disabled_macro, disabled_logger); + benchmark::RegisterBenchmark("disabled-at-compile-time (global logger)", + bench_disabled_macro_global_logger, disabled_logger); + benchmark::RegisterBenchmark("disabled-at-runtime", bench_logger, disabled_logger); + benchmark::RegisterBenchmark("disabled-at-runtime (global logger)", bench_global_logger, + disabled_logger); + // with backtrace of 64 + auto tracing_disabled_logger = + std::make_shared("bench", std::make_shared()); + tracing_disabled_logger->enable_backtrace(64); + benchmark::RegisterBenchmark("disabled-at-runtime/backtrace", bench_logger, + tracing_disabled_logger); + + auto null_logger_st = + std::make_shared("bench", std::make_shared()); + benchmark::RegisterBenchmark("null_sink_st (500_bytes c_str)", bench_c_string, + std::move(null_logger_st)); + benchmark::RegisterBenchmark("null_sink_st", bench_logger, null_logger_st); + benchmark::RegisterBenchmark("null_sink_st (global logger)", bench_global_logger, + null_logger_st); + // with backtrace of 64 + auto tracing_null_logger_st = + std::make_shared("bench", std::make_shared()); + tracing_null_logger_st->enable_backtrace(64); + benchmark::RegisterBenchmark("null_sink_st/backtrace", bench_logger, tracing_null_logger_st); + +#ifdef __linux__ + bench_dev_null(); +#endif // __linux__ + + if (full_bench) { + // basic_st + auto basic_st = spdlog::basic_logger_st("basic_st", "latency_logs/basic_st.log", true); + benchmark::RegisterBenchmark("basic_st", bench_logger, std::move(basic_st))->UseRealTime(); + spdlog::drop("basic_st"); + // with backtrace of 64 + auto tracing_basic_st = + spdlog::basic_logger_st("tracing_basic_st", "latency_logs/tracing_basic_st.log", true); + tracing_basic_st->enable_backtrace(64); + benchmark::RegisterBenchmark("basic_st/backtrace", bench_logger, + std::move(tracing_basic_st)) + ->UseRealTime(); + spdlog::drop("tracing_basic_st"); + + // rotating st + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "latency_logs/rotating_st.log", + file_size, rotating_files); + benchmark::RegisterBenchmark("rotating_st", bench_logger, std::move(rotating_st)) + ->UseRealTime(); + spdlog::drop("rotating_st"); + // with backtrace of 64 + auto tracing_rotating_st = spdlog::rotating_logger_st( + "tracing_rotating_st", "latency_logs/tracing_rotating_st.log", file_size, + rotating_files); + benchmark::RegisterBenchmark("rotating_st/backtrace", bench_logger, + std::move(tracing_rotating_st)) + ->UseRealTime(); + spdlog::drop("tracing_rotating_st"); + + // daily st + auto daily_st = spdlog::daily_logger_mt("daily_st", "latency_logs/daily_st.log"); + benchmark::RegisterBenchmark("daily_st", bench_logger, std::move(daily_st))->UseRealTime(); + spdlog::drop("daily_st"); + auto tracing_daily_st = + spdlog::daily_logger_mt("tracing_daily_st", "latency_logs/daily_st.log"); + benchmark::RegisterBenchmark("daily_st/backtrace", bench_logger, + std::move(tracing_daily_st)) + ->UseRealTime(); + spdlog::drop("tracing_daily_st"); + + // + // Multi threaded bench, 10 loggers using same logger concurrently + // + auto null_logger_mt = + std::make_shared("bench", std::make_shared()); + benchmark::RegisterBenchmark("null_sink_mt", bench_logger, null_logger_mt) + ->Threads(n_threads) + ->UseRealTime(); + + // basic_mt + auto basic_mt = spdlog::basic_logger_mt("basic_mt", "latency_logs/basic_mt.log", true); + benchmark::RegisterBenchmark("basic_mt", bench_logger, std::move(basic_mt)) + ->Threads(n_threads) + ->UseRealTime(); + spdlog::drop("basic_mt"); + + // rotating mt + auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "latency_logs/rotating_mt.log", + file_size, rotating_files); + benchmark::RegisterBenchmark("rotating_mt", bench_logger, std::move(rotating_mt)) + ->Threads(n_threads) + ->UseRealTime(); + spdlog::drop("rotating_mt"); + + // daily mt + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "latency_logs/daily_mt.log"); + benchmark::RegisterBenchmark("daily_mt", bench_logger, std::move(daily_mt)) + ->Threads(n_threads) + ->UseRealTime(); + spdlog::drop("daily_mt"); + } + + // async + auto queue_size = 1024 * 1024 * 3; + auto tp = std::make_shared(queue_size, 1); + auto async_logger = std::make_shared( + "async_logger", std::make_shared(), std::move(tp), + spdlog::async_overflow_policy::overrun_oldest); + benchmark::RegisterBenchmark("async_logger", bench_logger, async_logger) + ->Threads(n_threads) + ->UseRealTime(); + + auto async_logger_tracing = std::make_shared( + "async_logger_tracing", std::make_shared(), std::move(tp), + spdlog::async_overflow_policy::overrun_oldest); + async_logger_tracing->enable_backtrace(32); + benchmark::RegisterBenchmark("async_logger/tracing", bench_logger, async_logger_tracing) + ->Threads(n_threads) + ->UseRealTime(); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/lib/spdlog/bench/utils.h b/lib/spdlog/bench/utils.h new file mode 100644 index 0000000..1828fc0 --- /dev/null +++ b/lib/spdlog/bench/utils.h @@ -0,0 +1,32 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils { + +template +inline std::string format(const T &value) { + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template <> +inline std::string format(const double &value) { + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} // namespace utils diff --git a/lib/spdlog/cmake/ide.cmake b/lib/spdlog/cmake/ide.cmake new file mode 100644 index 0000000..a0656a5 --- /dev/null +++ b/lib/spdlog/cmake/ide.cmake @@ -0,0 +1,18 @@ +# --------------------------------------------------------------------------------------- +# IDE support for headers +# --------------------------------------------------------------------------------------- +set(SPDLOG_HEADERS_DIR "${CMAKE_CURRENT_LIST_DIR}/../include") + +file(GLOB SPDLOG_TOP_HEADERS "${SPDLOG_HEADERS_DIR}/spdlog/*.h") +file(GLOB SPDLOG_DETAILS_HEADERS "${SPDLOG_HEADERS_DIR}/spdlog/details/*.h") +file(GLOB SPDLOG_SINKS_HEADERS "${SPDLOG_HEADERS_DIR}/spdlog/sinks/*.h") +file(GLOB SPDLOG_FMT_HEADERS "${SPDLOG_HEADERS_DIR}/spdlog/fmt/*.h") +file(GLOB SPDLOG_FMT_BUNDELED_HEADERS "${SPDLOG_HEADERS_DIR}/spdlog/fmt/bundled/*.h") +set(SPDLOG_ALL_HEADERS ${SPDLOG_TOP_HEADERS} ${SPDLOG_DETAILS_HEADERS} ${SPDLOG_SINKS_HEADERS} ${SPDLOG_FMT_HEADERS} + ${SPDLOG_FMT_BUNDELED_HEADERS}) + +source_group("Header Files\\spdlog" FILES ${SPDLOG_TOP_HEADERS}) +source_group("Header Files\\spdlog\\details" FILES ${SPDLOG_DETAILS_HEADERS}) +source_group("Header Files\\spdlog\\sinks" FILES ${SPDLOG_SINKS_HEADERS}) +source_group("Header Files\\spdlog\\fmt" FILES ${SPDLOG_FMT_HEADERS}) +source_group("Header Files\\spdlog\\fmt\\bundled\\" FILES ${SPDLOG_FMT_BUNDELED_HEADERS}) diff --git a/lib/spdlog/cmake/pch.h.in b/lib/spdlog/cmake/pch.h.in new file mode 100644 index 0000000..0592ee9 --- /dev/null +++ b/lib/spdlog/cmake/pch.h.in @@ -0,0 +1,254 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// details/pattern_formatter-inl.h +// fmt/bin_to_hex.h +// fmt/bundled/format-inl.h +#include + +// details/file_helper-inl.h +// details/os-inl.h +// fmt/bundled/posix.h +// logger-inl.h +// sinks/daily_file_sink.h +// sinks/stdout_sinks.h +#include + +// details/os-inl.h +// fmt/bundled/posix.h +#include + +// details/os-inl.h +// details/pattern_formatter-inl.h +// fmt/bundled/format-inl.h +#include + +// details/os-inl.h +// details/os.h +// details/pattern_formatter-inl.h +// details/pattern_formatter.h +// fmt/bundled/chrono.h +// sinks/daily_file_sink.h +// sinks/rotating_file_sink-inl.h +#include + +// fmt/bundled/format-inl.h +#include + +// fmt/bundled/format-inl.h +#include + +// fmt/bundled/format-inl.h +// fmt/bundled/format.h +#include + +// fmt/bundled/format-inl.h +#include + +// details/file_helper-inl.h +// fmt/bundled/format.h +// fmt/bundled/posix.h +// sinks/rotating_file_sink-inl.h +#include + +// details/circular_q.h +// details/thread_pool-inl.h +// fmt/bundled/format-inl.h +#include + +// async_logger-inl.h +// cfg/helpers-inl.h +// log_levels.h +// common.h +// details/file_helper-inl.h +// details/log_msg.h +// details/os-inl.h +// details/pattern_formatter-inl.h +// details/pattern_formatter.h +// details/registry-inl.h +// details/registry.h +// details/tcp_client-windows.h +// details/tcp_client.h +// sinks/android_sink.h +// sinks/ansicolor_sink.h +// sinks/basic_file_sink.h +// sinks/daily_file_sink.h +// sinks/dup_filter_sink.h +// sinks/msvc_sink.h +// sinks/ringbuffer_sink.h +// sinks/rotating_file_sink-inl.h +// sinks/rotating_file_sink.h +// sinks/syslog_sink.h +// sinks/tcp_sink.h +// sinks/win_eventlog_sink.h +// sinks/wincolor_sink.h +// spdlog.h: +#include + +// cfg/helpers-inl.h +// fmt/bundled/chrono.h +#include + +// fmt/bundled/ostream.h +// sinks/ostream_sink.h +#include + +// cfg/log_levels.h +// details/registry-inl.h +// details/registry.h +#include + +// details/circular_q.h +// details/pattern_formatter-inl.h +// details/pattern_formatter.h +// details/thread_pool.h +// fmt/bundled/compile.h +// logger.h +// sinks/dist_sink.h +// sinks/ringbuffer_sink.h +// sinks/win_eventlog_sink.h +#include + +// details/os-inl.h +// details/pattern_formatter-inl.h +// sinks/ansicolor_sink.h +// sinks/syslog_sink.h +// sinks/systemd_sink.h +// sinks/wincolor_sink.h +#include + +// details/file_helper-inl.h +// details/file_helper.h +// sinks/rotating_file_sink-inl.h +#include + +// details/os-inl.h +// fmt/bundled/format.h +// fmt/bundled/printf.h +#include + +// common.h +// details/backtracer.h +// details/null_mutex.h +#include + +// common.h +// details/backtracer.h +// details/null_mutex.h +#include + +// common.h +#include + +// common.h +#include + +// common.h +// details/fmt_helper.h +// fmt/bundled/ranges.h +#include + +// cfg/helpers-inl.h +// details/null_mutex.h +// details/pattern_formatter-inl.h +#include + +// async.h +// async_logger-inl.h +// common.h +// details/pattern_formatter-inl.h +// details/pattern_formatter.h +// details/registry-inl.h +// details/registry.h +// details/thread_pool.h +// fmt/bundled/format.h +// sinks/ansicolor_sink.h +// sinks/base_sink-inl.h +// sinks/dist_sink.h +// sinks/stdout_sinks-inl.h +// sinks/wincolor_sink.h +// spdlog.h +#include + +// async.h +// common.h +// details/backtracer.h +// details/periodic_worker.h +// details/registry-inl.h +// details/registry.h +// details/thread_pool.h +// sinks/tcp_sink.h +// spdlog.h +#include + +// details/mpmc_blocking_q.h +// details/periodic_worker.h +#include + +// details/os-inl.h +// fmt/bundled/format.h +// fmt/bundled/printf.h +// sinks/dist_sink.h +#include + +// common.h +// details/file_helper-inl.h +// details/fmt_helper.h +// details/os-inl.h +// details/pattern_formatter-inl.h +// details/pattern_formatter.h +// details/periodic_worker.h +// details/registry-inl.h +// details/registry.h +// details/thread_pool.h +// fmt/bundled/chrono.h +// sinks/android_sink.h +// sinks/daily_file_sink.h +// sinks/dup_filter_sink.h +// sinks/rotating_file_sink-inl.h +// sinks/rotating_file_sink.h +// sinks/tcp_sink.h +// spdlog.h +#include + +// details/file_helper-inl.h +// details/os-inl.h +// details/pattern_formatter-inl.h +// details/periodic_worker.h +// details/thread_pool.h +// sinks/android_sink.h +#include + +// async.h +// details/backtracer.h +// details/console_globals.h +// details/mpmc_blocking_q.h +// details/pattern_formatter-inl.h +// details/periodic_worker.h +// details/registry.h +// sinks/android_sink.h +// sinks/ansicolor_sink.h +// sinks/basic_file_sink.h +// sinks/daily_file_sink.h +// sinks/dist_sink.h +// sinks/dup_filter_sink.h +// sinks/msvc_sink.h +// sinks/null_sink.h +// sinks/ostream_sink.h +// sinks/ringbuffer_sink.h +// sinks/rotating_file_sink-inl.h +// sinks/rotating_file_sink.h +// sinks/tcp_sink.h +// sinks/win_eventlog_sink.h +// sinks/wincolor_sink.h +// +// color_sinks.cpp +// file_sinks.cpp +// spdlog.cpp +// stdout_sinks.cpp +#include + +// spdlog +#include diff --git a/lib/spdlog/cmake/spdlog.pc.in b/lib/spdlog/cmake/spdlog.pc.in new file mode 100644 index 0000000..ffab5d6 --- /dev/null +++ b/lib/spdlog/cmake/spdlog.pc.in @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=@PKG_CONFIG_INCLUDEDIR@ +libdir=@PKG_CONFIG_LIBDIR@ + +Name: lib@PROJECT_NAME@ +Description: Fast C++ logging library. +URL: https://github.com/gabime/@PROJECT_NAME@ +Version: @SPDLOG_VERSION@ +CFlags: -I${includedir} @PKG_CONFIG_DEFINES@ +Libs: -L${libdir} -lspdlog -pthread +Requires: @PKG_CONFIG_REQUIRES@ + diff --git a/lib/spdlog/cmake/spdlogCPack.cmake b/lib/spdlog/cmake/spdlogCPack.cmake new file mode 100644 index 0000000..58bf401 --- /dev/null +++ b/lib/spdlog/cmake/spdlogCPack.cmake @@ -0,0 +1,60 @@ +set(CPACK_GENERATOR "TGZ;ZIP" CACHE STRING "Semicolon separated list of generators") + +set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0) +set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR}" "${PROJECT_NAME}" ALL .) + +set(CPACK_PROJECT_URL "https://github.com/gabime/spdlog") +set(CPACK_PACKAGE_VENDOR "Gabi Melman") +set(CPACK_PACKAGE_CONTACT "Gabi Melman ") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Fast C++ logging library") +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) +if(PROJECT_VERSION_TWEAK) + set(CPACK_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}.${PROJECT_VERSION_TWEAK}) +endif() +set(CPACK_PACKAGE_RELOCATABLE ON CACHE BOOL "Build relocatable package") + +set(CPACK_RPM_PACKAGE_LICENSE "MIT") +set(CPACK_RPM_PACKAGE_GROUP "Development/Libraries") +set(CPACK_DEBIAN_PACKAGE_SECTION "libs") +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_DEBIAN_PACKAGE_DESCRIPTION "Very fast, header-only/compiled, C++ logging library.") + +if(CPACK_PACKAGE_NAME) + set(CPACK_RPM_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") +else() + 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() + +if(CPACK_RPM_PACKAGE_RELEASE) + set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}-${CPACK_RPM_PACKAGE_RELEASE}") +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) + set(CPACK_RPM_FILE_NAME "${CPACK_RPM_FILE_NAME}.${CPACK_RPM_PACKAGE_ARCHITECTURE}") +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_DEBIAN_FILE_NAME "${CPACK_DEBIAN_FILE_NAME}.deb") + +if(NOT CPACK_PACKAGE_RELOCATABLE) + # Depend on pkgconfig rpm to create the system pkgconfig folder + set(CPACK_RPM_PACKAGE_REQUIRES pkgconfig) + set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION + "${CPACK_PACKAGING_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/pkgconfig") +endif() + +include(CPack) diff --git a/lib/spdlog/cmake/spdlogConfig.cmake.in b/lib/spdlog/cmake/spdlogConfig.cmake.in new file mode 100644 index 0000000..1b85b9e --- /dev/null +++ b/lib/spdlog/cmake/spdlogConfig.cmake.in @@ -0,0 +1,20 @@ +# Copyright(c) 2019 spdlog authors +# Distributed under the MIT License (http://opensource.org/licenses/MIT) + +@PACKAGE_INIT@ + +find_package(Threads REQUIRED) + +set(SPDLOG_FMT_EXTERNAL @SPDLOG_FMT_EXTERNAL@) +set(SPDLOG_FMT_EXTERNAL_HO @SPDLOG_FMT_EXTERNAL_HO@) +set(config_targets_file @config_targets_file@) + +if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO) + include(CMakeFindDependencyMacro) + find_dependency(fmt CONFIG) +endif() + + +include("${CMAKE_CURRENT_LIST_DIR}/${config_targets_file}") + +check_required_components(spdlog) diff --git a/lib/spdlog/cmake/utils.cmake b/lib/spdlog/cmake/utils.cmake new file mode 100644 index 0000000..8926657 --- /dev/null +++ b/lib/spdlog/cmake/utils.cmake @@ -0,0 +1,73 @@ +# Get spdlog version from include/spdlog/version.h and put it in SPDLOG_VERSION +function(spdlog_extract_version) + file(READ "${CMAKE_CURRENT_LIST_DIR}/include/spdlog/version.h" file_contents) + string(REGEX MATCH "SPDLOG_VER_MAJOR ([0-9]+)" _ "${file_contents}") + if(NOT CMAKE_MATCH_COUNT EQUAL 1) + message(FATAL_ERROR "Could not extract major version number from spdlog/version.h") + endif() + set(ver_major ${CMAKE_MATCH_1}) + + string(REGEX MATCH "SPDLOG_VER_MINOR ([0-9]+)" _ "${file_contents}") + if(NOT CMAKE_MATCH_COUNT EQUAL 1) + message(FATAL_ERROR "Could not extract minor version number from spdlog/version.h") + endif() + + set(ver_minor ${CMAKE_MATCH_1}) + string(REGEX MATCH "SPDLOG_VER_PATCH ([0-9]+)" _ "${file_contents}") + if(NOT CMAKE_MATCH_COUNT EQUAL 1) + message(FATAL_ERROR "Could not extract patch version number from spdlog/version.h") + endif() + set(ver_patch ${CMAKE_MATCH_1}) + + set(SPDLOG_VERSION_MAJOR ${ver_major} PARENT_SCOPE) + set(SPDLOG_VERSION_MINOR ${ver_minor} PARENT_SCOPE) + set(SPDLOG_VERSION_PATCH ${ver_patch} PARENT_SCOPE) + set(SPDLOG_VERSION "${ver_major}.${ver_minor}.${ver_patch}" PARENT_SCOPE) +endfunction() + +# Turn on warnings on the given target +function(spdlog_enable_warnings target_name) + if(SPDLOG_BUILD_WARNINGS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + list(APPEND MSVC_OPTIONS "/W3") + if(MSVC_VERSION GREATER 1900) # Allow non fatal security warnings for msvc 2015 + list(APPEND MSVC_OPTIONS "/WX") + endif() + endif() + + target_compile_options( + ${target_name} + PRIVATE $<$,$,$>: + -Wall + -Wextra + -Wconversion + -pedantic + -Werror + -Wfatal-errors> + $<$:${MSVC_OPTIONS}>) + endif() +endfunction() + +# Enable address sanitizer (gcc/clang only) +function(spdlog_enable_addr_sanitizer target_name) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + message(FATAL_ERROR "Sanitizer supported only for gcc/clang") + endif() + message(STATUS "Address sanitizer enabled") + target_compile_options(${target_name} PRIVATE -fsanitize=address,undefined) + target_compile_options(${target_name} PRIVATE -fno-sanitize=signed-integer-overflow) + target_compile_options(${target_name} PRIVATE -fno-sanitize-recover=all) + target_compile_options(${target_name} PRIVATE -fno-omit-frame-pointer) + target_link_libraries(${target_name} PRIVATE -fsanitize=address,undefined) +endfunction() + +# Enable thread sanitizer (gcc/clang only) +function(spdlog_enable_thread_sanitizer target_name) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + message(FATAL_ERROR "Sanitizer supported only for gcc/clang") + endif() + message(STATUS "Thread sanitizer enabled") + target_compile_options(${target_name} PRIVATE -fsanitize=thread) + target_compile_options(${target_name} PRIVATE -fno-omit-frame-pointer) + target_link_libraries(${target_name} PRIVATE -fsanitize=thread) +endfunction() diff --git a/lib/spdlog/cmake/version.rc.in b/lib/spdlog/cmake/version.rc.in new file mode 100644 index 0000000..a86c138 --- /dev/null +++ b/lib/spdlog/cmake/version.rc.in @@ -0,0 +1,42 @@ +#define APSTUDIO_READONLY_SYMBOLS +#include +#undef APSTUDIO_READONLY_SYMBOLS + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @SPDLOG_VERSION_MAJOR@,@SPDLOG_VERSION_MINOR@,@SPDLOG_VERSION_PATCH@,0 + PRODUCTVERSION @SPDLOG_VERSION_MAJOR@,@SPDLOG_VERSION_MINOR@,@SPDLOG_VERSION_PATCH@,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "spdlog dll\0" + VALUE "FileVersion", "@SPDLOG_VERSION@.0\0" + VALUE "InternalName", "spdlog.dll\0" + VALUE "LegalCopyright", "Copyright (C) spdlog\0" + VALUE "ProductName", "spdlog\0" + VALUE "ProductVersion", "@SPDLOG_VERSION@.0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + + + + diff --git a/lib/spdlog/example/CMakeLists.txt b/lib/spdlog/example/CMakeLists.txt new file mode 100644 index 0000000..da1ed4e --- /dev/null +++ b/lib/spdlog/example/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright(c) 2019 spdlog authors Distributed under the MIT License (http://opensource.org/licenses/MIT) + +cmake_minimum_required(VERSION 3.11) +project(spdlog_examples CXX) + +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog REQUIRED) +endif() + +# --------------------------------------------------------------------------------------- +# Example of using pre-compiled library +# --------------------------------------------------------------------------------------- +add_executable(example example.cpp) +target_link_libraries(example PRIVATE spdlog::spdlog $<$:ws2_32>) + +# --------------------------------------------------------------------------------------- +# Example of using header-only library +# --------------------------------------------------------------------------------------- +if(SPDLOG_BUILD_EXAMPLE_HO) + add_executable(example_header_only example.cpp) + target_link_libraries(example_header_only PRIVATE spdlog::spdlog_header_only) +endif() diff --git a/lib/spdlog/example/example.cpp b/lib/spdlog/example/example.cpp new file mode 100644 index 0000000..8d57d0c --- /dev/null +++ b/lib/spdlog/example/example.cpp @@ -0,0 +1,401 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +// spdlog usage example + +#include +#include + +void load_levels_example(); +void stdout_logger_example(); +void basic_example(); +void rotating_example(); +void daily_example(); +void callback_example(); +void async_example(); +void binary_example(); +void vector_example(); +void stopwatch_example(); +void trace_example(); +void multi_sink_example(); +void user_defined_example(); +void err_handler_example(); +void syslog_example(); +void udp_example(); +void custom_flags_example(); +void file_events_example(); +void replace_default_logger_example(); +void mdc_example(); + +#include "spdlog/spdlog.h" +#include "spdlog/cfg/env.h" // support for loading levels from the environment variable +#include "spdlog/fmt/ostr.h" // support for user defined types + +int main(int, char *[]) { + try { + // Log levels can be loaded from argv/env using "SPDLOG_LEVEL" + load_levels_example(); + + spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, + SPDLOG_VER_PATCH); + + spdlog::warn("Easy padding in numbers like {:08d}", 12); + spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); + spdlog::info("Support for floats {:03.2f}", 1.23456); + spdlog::info("Positional args are {1} {0}..", "too", "supported"); + spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left"); + + // Runtime log levels + spdlog::set_level(spdlog::level::info); // Set global log level to info + spdlog::debug("This message should not be displayed!"); + spdlog::set_level(spdlog::level::trace); // Set specific logger's log level + spdlog::debug("This message should be displayed.."); + + // Customize msg format for all loggers + spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v"); + spdlog::info("This an info message with custom format"); + spdlog::set_pattern("%+"); // back to default format + spdlog::set_level(spdlog::level::info); + + // Backtrace support + // Loggers can store in a ring buffer all messages (including debug/trace) for later + // inspection. When needed, call dump_backtrace() to see what happened: + spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages + for (int i = 0; i < 100; i++) { + spdlog::debug("Backtrace message {}", i); // not logged.. + } + // e.g. if some error happened: + spdlog::dump_backtrace(); // log them now! + + stdout_logger_example(); + basic_example(); + rotating_example(); + daily_example(); + callback_example(); + async_example(); + binary_example(); + vector_example(); + multi_sink_example(); + user_defined_example(); + err_handler_example(); + trace_example(); + stopwatch_example(); + udp_example(); + custom_flags_example(); + file_events_example(); + replace_default_logger_example(); + mdc_example(); + + // Flush all *registered* loggers using a worker thread every 3 seconds. + // note: registered loggers *must* be thread safe for this to work correctly! + spdlog::flush_every(std::chrono::seconds(3)); + + // Apply some function on all registered loggers + spdlog::apply_all([&](std::shared_ptr l) { l->info("End of example."); }); + + // Release all spdlog resources, and drop all loggers in the registry. + // This is optional (only mandatory if using windows + async log). + spdlog::shutdown(); + } + + // Exceptions will only be thrown upon failed logger or sink construction (not during logging). + catch (const spdlog::spdlog_ex &ex) { + std::printf("Log initialization failed: %s\n", ex.what()); + return 1; + } +} + +#include "spdlog/sinks/stdout_color_sinks.h" +// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed. +void stdout_logger_example() { + // Create color multi threaded logger. + auto console = spdlog::stdout_color_mt("console"); + // or for stderr: + // auto console = spdlog::stderr_color_mt("error-logger"); +} + +#include "spdlog/sinks/basic_file_sink.h" +void basic_example() { + // Create basic file logger (not rotated). + auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true); +} + +#include "spdlog/sinks/rotating_file_sink.h" +void rotating_example() { + // Create a file rotating logger with 5mb size max and 3 rotated files. + auto rotating_logger = + spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); +} + +#include "spdlog/sinks/daily_file_sink.h" +void daily_example() { + // Create a daily logger - a new file is created every day on 2:30am. + auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); +} + +#include "spdlog/sinks/callback_sink.h" +void callback_example() { + // Create the logger + auto logger = spdlog::callback_logger_mt("custom_callback_logger", + [](const spdlog::details::log_msg & /*msg*/) { + // do what you need to do with msg + }); +} + +#include "spdlog/cfg/env.h" +void load_levels_example() { + // Set the log level to "info" and mylogger to "trace": + // SPDLOG_LEVEL=info,mylogger=trace && ./example + spdlog::cfg::load_env_levels(); + // or specify the env variable name: + // MYAPP_LEVEL=info,mylogger=trace && ./example + // spdlog::cfg::load_env_levels("MYAPP_LEVEL"); + // or from command line: + // ./example SPDLOG_LEVEL=info,mylogger=trace + // #include "spdlog/cfg/argv.h" // for loading levels from argv + // spdlog::cfg::load_argv_levels(args, argv); +} + +#include "spdlog/async.h" +void async_example() { + // Default thread pool settings can be modified *before* creating the async logger: + // spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread. + auto async_file = + spdlog::basic_logger_mt("async_file_logger", "logs/async_log.txt"); + // alternatively: + // auto async_file = + // spdlog::create_async("async_file_logger", + // "logs/async_log.txt"); + + for (int i = 1; i < 101; ++i) { + async_file->info("Async message #{}", i); + } +} + +// Log binary data as hex. +// Many types of std::container types can be used. +// Iterator ranges are supported too. +// Format flags: +// {:X} - print in uppercase. +// {:s} - don't separate each byte with space. +// {:p} - don't print the position on each line start. +// {:n} - don't split the output to lines. + +#if !defined SPDLOG_USE_STD_FORMAT || defined(_MSC_VER) +#include "spdlog/fmt/bin_to_hex.h" +void binary_example() { + std::vector buf; + for (int i = 0; i < 80; i++) { + buf.push_back(static_cast(i & 0xff)); + } + spdlog::info("Binary example: {}", spdlog::to_hex(buf)); + spdlog::info("Another binary example:{:n}", + spdlog::to_hex(std::begin(buf), std::begin(buf) + 10)); + // more examples: + // logger->info("uppercase: {:X}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf)); + // logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf)); + // logger->info("hexdump style: {:a}", spdlog::to_hex(buf)); + // logger->info("hexdump style, 20 chars per line {:a}", spdlog::to_hex(buf, 20)); +} +#else +void binary_example() { + // not supported with std::format yet +} +#endif + +// Log a vector of numbers +#ifndef SPDLOG_USE_STD_FORMAT +#include "spdlog/fmt/ranges.h" +void vector_example() { + std::vector vec = {1, 2, 3}; + spdlog::info("Vector example: {}", vec); +} + +#else +void vector_example() {} +#endif + +// ! DSPDLOG_USE_STD_FORMAT + +// Compile time log levels. +// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE) +void trace_example() { + // trace from default logger + SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23); + // debug from default logger + SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23); + + // trace from logger object + auto logger = spdlog::get("file_logger"); + SPDLOG_LOGGER_TRACE(logger, "another trace message"); +} + +// stopwatch example +#include "spdlog/stopwatch.h" +#include +void stopwatch_example() { + spdlog::stopwatch sw; + std::this_thread::sleep_for(std::chrono::milliseconds(123)); + spdlog::info("Stopwatch: {} seconds", sw); +} + +#include "spdlog/sinks/udp_sink.h" +void udp_example() { + spdlog::sinks::udp_sink_config cfg("127.0.0.1", 11091); + auto my_logger = spdlog::udp_logger_mt("udplog", cfg); + my_logger->set_level(spdlog::level::debug); + my_logger->info("hello world"); +} + +// A logger with multiple sinks (stdout and file) - each with a different format and log level. +void multi_sink_example() { + auto console_sink = std::make_shared(); + console_sink->set_level(spdlog::level::warn); + console_sink->set_pattern("[multi_sink_example] [%^%l%$] %v"); + + auto file_sink = + std::make_shared("logs/multisink.txt", true); + file_sink->set_level(spdlog::level::trace); + + spdlog::logger logger("multi_sink", {console_sink, file_sink}); + logger.set_level(spdlog::level::debug); + logger.warn("this should appear in both console and file"); + logger.info("this message should not appear in the console, only in the file"); +} + +// User defined types logging +struct my_type { + int value_ = 0; + explicit my_type(int value) + : value_(value) {} +}; + +#ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib +template <> +struct fmt::formatter : fmt::formatter { + auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) { + return fmt::format_to(ctx.out(), "[my_type value={}]", my.value_); + } +}; + +#else // when using std::format +template <> +struct std::formatter : std::formatter { + auto format(my_type my, format_context &ctx) const -> decltype(ctx.out()) { + return std::format_to(ctx.out(), "[my_type value={}]", my.value_); + } +}; +#endif + +void user_defined_example() { spdlog::info("user defined type: {}", my_type(14)); } + +// Custom error handler. Will be triggered on log failure. +void err_handler_example() { + // can be set globally or per logger(logger->set_error_handler(..)) + spdlog::set_error_handler([](const std::string &msg) { + printf("*** Custom log error handler: %s ***\n", msg.c_str()); + }); +} + +// syslog example (linux/osx/freebsd) +#ifndef _WIN32 +#include "spdlog/sinks/syslog_sink.h" +void syslog_example() { + std::string ident = "spdlog-example"; + auto syslog_logger = spdlog::syslog_logger_mt("syslog", ident, LOG_PID); + syslog_logger->warn("This is warning that will end up in syslog."); +} +#endif + +// Android example. +#if defined(__ANDROID__) +#include "spdlog/sinks/android_sink.h" +void android_example() { + std::string tag = "spdlog-android"; + auto android_logger = spdlog::android_logger_mt("android", tag); + android_logger->critical("Use \"adb shell logcat\" to view this message."); +} +#endif + +// Log patterns can contain custom flags. +// this will add custom flag '%*' which will be bound to a instance +#include "spdlog/pattern_formatter.h" +class my_formatter_flag : public spdlog::custom_flag_formatter { +public: + void format(const spdlog::details::log_msg &, + const std::tm &, + spdlog::memory_buf_t &dest) override { + std::string some_txt = "custom-flag"; + dest.append(some_txt.data(), some_txt.data() + some_txt.size()); + } + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(); + } +}; + +void custom_flags_example() { + using spdlog::details::make_unique; // for pre c++14 + auto formatter = make_unique(); + formatter->add_flag('*').set_pattern("[%n] [%*] [%^%l%$] %v"); + // set the new formatter using spdlog::set_formatter(formatter) or + // logger->set_formatter(formatter) spdlog::set_formatter(std::move(formatter)); +} + +void file_events_example() { + // pass the spdlog::file_event_handlers to file sinks for open/close log file notifications + spdlog::file_event_handlers handlers; + handlers.before_open = [](spdlog::filename_t filename) { + spdlog::info("Before opening {}", filename); + }; + handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { + spdlog::info("After opening {}", filename); + fputs("After opening\n", fstream); + }; + handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { + spdlog::info("Before closing {}", filename); + fputs("Before closing\n", fstream); + }; + handlers.after_close = [](spdlog::filename_t filename) { + spdlog::info("After closing {}", filename); + }; + auto file_sink = std::make_shared("logs/events-sample.txt", + true, handlers); + spdlog::logger my_logger("some_logger", file_sink); + my_logger.info("Some log line"); +} + +void replace_default_logger_example() { + // store the old logger so we don't break other examples. + auto old_logger = spdlog::default_logger(); + + auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/somelog.txt", true); + spdlog::set_default_logger(std::move(new_logger)); + spdlog::set_level(spdlog::level::info); + spdlog::debug("This message should not be displayed!"); + spdlog::set_level(spdlog::level::trace); + spdlog::debug("This message should be displayed.."); + spdlog::set_default_logger(std::move(old_logger)); +} + +// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread +// local storage. Each thread maintains its own MDC, which loggers use to append diagnostic +// information to log outputs. Note: it is not supported in asynchronous mode due to its reliance on +// thread-local storage. + +#ifndef SPDLOG_NO_TLS +#include "spdlog/mdc.h" +void mdc_example() { + spdlog::mdc::put("key1", "value1"); + spdlog::mdc::put("key2", "value2"); + // if not using the default format, you can use the %& formatter to print mdc data as well + spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [%&] %v"); + spdlog::info("Some log message with context"); +} +#else +void mdc_example() { + // if TLS feature is disabled +} +#endif diff --git a/lib/spdlog/include/spdlog/async.h b/lib/spdlog/include/spdlog/async.h new file mode 100644 index 0000000..92fcd9a --- /dev/null +++ b/lib/spdlog/include/spdlog/async.h @@ -0,0 +1,99 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// +// Async logging using global thread pool +// All loggers created here share same global thread pool. +// Each log message is pushed to a queue along with a shared pointer to the +// logger. +// If a logger deleted while having pending messages in the queue, it's actual +// destruction will defer +// until all its messages are processed by the thread pool. +// This is because each message in the queue holds a shared_ptr to the +// originating logger. + +#include +#include +#include + +#include +#include +#include + +namespace spdlog { + +namespace details { +static const size_t default_async_q_size = 8192; +} + +// async logger factory - creates async loggers backed with thread pool. +// if a global thread pool doesn't already exist, create it with default queue +// size of 8192 items and single thread. +template +struct async_factory_impl { + template + static std::shared_ptr create(std::string logger_name, SinkArgs &&...args) { + auto ®istry_inst = details::registry::instance(); + + // create global thread pool if not already exists.. + + auto &mutex = registry_inst.tp_mutex(); + std::lock_guard tp_lock(mutex); + auto tp = registry_inst.get_tp(); + if (tp == nullptr) { + tp = std::make_shared(details::default_async_q_size, 1U); + registry_inst.set_tp(tp); + } + + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(std::move(logger_name), std::move(sink), + std::move(tp), OverflowPolicy); + registry_inst.initialize_logger(new_logger); + return new_logger; + } +}; + +using async_factory = async_factory_impl; +using async_factory_nonblock = async_factory_impl; + +template +inline std::shared_ptr create_async(std::string logger_name, + SinkArgs &&...sink_args) { + return async_factory::create(std::move(logger_name), + std::forward(sink_args)...); +} + +template +inline std::shared_ptr create_async_nb(std::string logger_name, + SinkArgs &&...sink_args) { + return async_factory_nonblock::create(std::move(logger_name), + std::forward(sink_args)...); +} + +// set global thread pool. +inline void init_thread_pool(size_t q_size, + size_t thread_count, + std::function on_thread_start, + std::function on_thread_stop) { + auto tp = std::make_shared(q_size, thread_count, on_thread_start, + on_thread_stop); + details::registry::instance().set_tp(std::move(tp)); +} + +inline void init_thread_pool(size_t q_size, + size_t thread_count, + std::function on_thread_start) { + init_thread_pool(q_size, thread_count, on_thread_start, [] {}); +} + +inline void init_thread_pool(size_t q_size, size_t thread_count) { + init_thread_pool(q_size, thread_count, [] {}, [] {}); +} + +// get the global thread pool. +inline std::shared_ptr thread_pool() { + return details::registry::instance().get_tp(); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/async_logger-inl.h b/lib/spdlog/include/spdlog/async_logger-inl.h new file mode 100644 index 0000000..382ef07 --- /dev/null +++ b/lib/spdlog/include/spdlog/async_logger-inl.h @@ -0,0 +1,84 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +#include +#include + +SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name, + sinks_init_list sinks_list, + std::weak_ptr tp, + async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), + sinks_list.begin(), + sinks_list.end(), + std::move(tp), + overflow_policy) {} + +SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name, + sink_ptr single_sink, + std::weak_ptr tp, + async_overflow_policy overflow_policy) + : async_logger( + std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) {} + +// send the log message to the thread pool +SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){ + SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ + pool_ptr -> post_log(shared_from_this(), msg, overflow_policy_); +} +else { + throw_spdlog_ex("async log: thread pool doesn't exist anymore"); +} +} +SPDLOG_LOGGER_CATCH(msg.source) +} + +// send flush request to the thread pool +SPDLOG_INLINE void spdlog::async_logger::flush_(){ + SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ + pool_ptr -> post_flush(shared_from_this(), overflow_policy_); +} +else { + throw_spdlog_ex("async flush: thread pool doesn't exist anymore"); +} +} +SPDLOG_LOGGER_CATCH(source_loc()) +} + +// +// backend functions - called from the thread pool to do the actual job +// +SPDLOG_INLINE void spdlog::async_logger::backend_sink_it_(const details::log_msg &incoming_log_msg) { + for (auto &sink : sinks_) { + if (sink->should_log(incoming_log_msg.level)) { + SPDLOG_TRY { sink->log(incoming_log_msg); } + SPDLOG_LOGGER_CATCH(incoming_log_msg.source) + } + } + + if (should_flush_(incoming_log_msg)) { + backend_flush_(); + } +} + +SPDLOG_INLINE void spdlog::async_logger::backend_flush_() { + for (auto &sink : sinks_) { + SPDLOG_TRY { sink->flush(); } + SPDLOG_LOGGER_CATCH(source_loc()) + } +} + +SPDLOG_INLINE std::shared_ptr spdlog::async_logger::clone(std::string new_name) { + auto cloned = std::make_shared(*this); + cloned->name_ = std::move(new_name); + return cloned; +} diff --git a/lib/spdlog/include/spdlog/async_logger.h b/lib/spdlog/include/spdlog/async_logger.h new file mode 100644 index 0000000..be36153 --- /dev/null +++ b/lib/spdlog/include/spdlog/async_logger.h @@ -0,0 +1,74 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// Fast asynchronous logger. +// Uses pre allocated queue. +// Creates a single back thread to pop messages from the queue and log them. +// +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until +// space is available in the queue) +// Upon destruction, logs all remaining messages in the queue before +// destructing.. + +#include + +namespace spdlog { + +// Async overflow policy - block by default. +enum class async_overflow_policy { + block, // Block until message can be enqueued + overrun_oldest, // Discard oldest message in the queue if full when trying to + // add new item. + discard_new // Discard new message if the queue is full when trying to add new item. +}; + +namespace details { +class thread_pool; +} + +class SPDLOG_API async_logger final : public std::enable_shared_from_this, + public logger { + friend class details::thread_pool; + +public: + template + async_logger(std::string logger_name, + It begin, + It end, + std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block) + : logger(std::move(logger_name), begin, end), + thread_pool_(std::move(tp)), + overflow_policy_(overflow_policy) {} + + async_logger(std::string logger_name, + sinks_init_list sinks_list, + std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + async_logger(std::string logger_name, + sink_ptr single_sink, + std::weak_ptr tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + std::shared_ptr clone(std::string new_name) override; + +protected: + void sink_it_(const details::log_msg &msg) override; + void flush_() override; + void backend_sink_it_(const details::log_msg &incoming_log_msg); + void backend_flush_(); + +private: + std::weak_ptr thread_pool_; + async_overflow_policy overflow_policy_; +}; +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "async_logger-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/cfg/argv.h b/lib/spdlog/include/spdlog/cfg/argv.h new file mode 100644 index 0000000..64cf389 --- /dev/null +++ b/lib/spdlog/include/spdlog/cfg/argv.h @@ -0,0 +1,40 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once +#include +#include + +// +// Init log levels using each argv entry that starts with "SPDLOG_LEVEL=" +// +// set all loggers to debug level: +// example.exe "SPDLOG_LEVEL=debug" + +// set logger1 to trace level +// example.exe "SPDLOG_LEVEL=logger1=trace" + +// turn off all logging except for logger1 and logger2: +// example.exe "SPDLOG_LEVEL=off,logger1=debug,logger2=info" + +namespace spdlog { +namespace cfg { + +// search for SPDLOG_LEVEL= in the args and use it to init the levels +inline void load_argv_levels(int argc, const char **argv) { + const std::string spdlog_level_prefix = "SPDLOG_LEVEL="; + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg.find(spdlog_level_prefix) == 0) { + const auto levels_spec = arg.substr(spdlog_level_prefix.size()); + helpers::load_levels(levels_spec); + } + } +} + +inline void load_argv_levels(int argc, char **argv) { + load_argv_levels(argc, const_cast(argv)); +} + +} // namespace cfg +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/cfg/env.h b/lib/spdlog/include/spdlog/cfg/env.h new file mode 100644 index 0000000..cb153e0 --- /dev/null +++ b/lib/spdlog/include/spdlog/cfg/env.h @@ -0,0 +1,36 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once +#include +#include +#include + +// +// Init levels and patterns from env variables SPDLOG_LEVEL +// Inspired from Rust's "env_logger" crate (https://crates.io/crates/env_logger). +// Note - fallback to "info" level on unrecognized levels +// +// Examples: +// +// set global level to debug: +// export SPDLOG_LEVEL=debug +// +// turn off all logging except for logger1: +// export SPDLOG_LEVEL="*=off,logger1=debug" +// + +// turn off all logging except for logger1 and logger2: +// export SPDLOG_LEVEL="off,logger1=debug,logger2=info" + +namespace spdlog { +namespace cfg { +inline void load_env_levels(const char* var = "SPDLOG_LEVEL") { + const auto levels_spec = details::os::getenv(var); + if (!levels_spec.empty()) { + helpers::load_levels(levels_spec); + } +} + +} // namespace cfg +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/cfg/helpers-inl.h b/lib/spdlog/include/spdlog/cfg/helpers-inl.h new file mode 100644 index 0000000..0105036 --- /dev/null +++ b/lib/spdlog/include/spdlog/cfg/helpers-inl.h @@ -0,0 +1,106 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +#include +#include +#include +#include + +namespace spdlog { +namespace cfg { +namespace helpers { + +// inplace convert to lowercase +inline std::string &to_lower_(std::string &str) { + std::transform(str.begin(), str.end(), str.begin(), [](char ch) { + return static_cast((ch >= 'A' && ch <= 'Z') ? ch + ('a' - 'A') : ch); + }); + return str; +} + +// inplace trim spaces +inline std::string &trim_(std::string &str) { + const char *spaces = " \n\r\t"; + str.erase(str.find_last_not_of(spaces) + 1); + str.erase(0, str.find_first_not_of(spaces)); + return str; +} + +// return (name,value) trimmed pair from the given "name = value" string. +// return empty string on missing parts +// "key=val" => ("key", "val") +// " key = val " => ("key", "val") +// "key=" => ("key", "") +// "val" => ("", "val") + +inline std::pair extract_kv_(char sep, const std::string &str) { + auto n = str.find(sep); + std::string k, v; + if (n == std::string::npos) { + v = str; + } else { + k = str.substr(0, n); + v = str.substr(n + 1); + } + return std::make_pair(trim_(k), trim_(v)); +} + +// return vector of key/value pairs from a sequence of "K1=V1,K2=V2,.." +// "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...} +inline std::unordered_map extract_key_vals_(const std::string &str) { + std::string token; + std::istringstream token_stream(str); + std::unordered_map rv{}; + while (std::getline(token_stream, token, ',')) { + if (token.empty()) { + continue; + } + auto kv = extract_kv_('=', token); + rv[kv.first] = kv.second; + } + return rv; +} + +SPDLOG_INLINE void load_levels(const std::string &levels_spec) { + if (levels_spec.empty() || levels_spec.size() >= 32768) { + return; + } + + auto key_vals = extract_key_vals_(levels_spec); + std::unordered_map levels; + level::level_enum global_level = level::info; + bool global_level_found = false; + + for (auto &name_level : key_vals) { + const auto &logger_name = name_level.first; + const auto &level_name = to_lower_(name_level.second); + const auto level = level::from_str(level_name); + // ignore unrecognized level names + if (level == level::off && level_name != "off") { + continue; + } + if (logger_name.empty()) // no logger name indicates global level + { + global_level_found = true; + global_level = level; + } else { + levels[logger_name] = level; + } + } + + details::registry::instance().set_levels(std::move(levels), + global_level_found ? &global_level : nullptr); +} + +} // namespace helpers +} // namespace cfg +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/cfg/helpers.h b/lib/spdlog/include/spdlog/cfg/helpers.h new file mode 100644 index 0000000..25590e4 --- /dev/null +++ b/lib/spdlog/include/spdlog/cfg/helpers.h @@ -0,0 +1,29 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +namespace spdlog { +namespace cfg { +namespace helpers { +// +// Init levels from given string +// +// Examples: +// +// set global level to debug: "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" +// +SPDLOG_API void load_levels(const std::string &levels_spec); +} // namespace helpers + +} // namespace cfg +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "helpers-inl.h" +#endif // SPDLOG_HEADER_ONLY diff --git a/lib/spdlog/include/spdlog/common-inl.h b/lib/spdlog/include/spdlog/common-inl.h new file mode 100644 index 0000000..f35901c --- /dev/null +++ b/lib/spdlog/include/spdlog/common-inl.h @@ -0,0 +1,68 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace level { + +#if __cplusplus >= 201703L +constexpr +#endif + static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES; + +static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES; + +SPDLOG_INLINE const string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT { + return level_string_views[l]; +} + +SPDLOG_INLINE const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT { + return short_level_names[l]; +} + +SPDLOG_INLINE spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT { + auto it = std::find(std::begin(level_string_views), std::end(level_string_views), name); + if (it != std::end(level_string_views)) + return static_cast(std::distance(std::begin(level_string_views), it)); + + // check also for "warn" and "err" before giving up.. + if (name == "warn") { + return level::warn; + } + if (name == "err") { + return level::err; + } + return level::off; +} +} // namespace level + +SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg) + : msg_(std::move(msg)) {} + +SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno) { +#ifdef SPDLOG_USE_STD_FORMAT + msg_ = std::system_error(std::error_code(last_errno, std::generic_category()), msg).what(); +#else + memory_buf_t outbuf; + fmt::format_system_error(outbuf, last_errno, msg.c_str()); + msg_ = fmt::to_string(outbuf); +#endif +} + +SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT { return msg_.c_str(); } + +SPDLOG_INLINE void throw_spdlog_ex(const std::string &msg, int last_errno) { + SPDLOG_THROW(spdlog_ex(msg, last_errno)); +} + +SPDLOG_INLINE void throw_spdlog_ex(std::string msg) { SPDLOG_THROW(spdlog_ex(std::move(msg))); } + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/common.h b/lib/spdlog/include/spdlog/common.h new file mode 100644 index 0000000..20dbca4 --- /dev/null +++ b/lib/spdlog/include/spdlog/common.h @@ -0,0 +1,406 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SPDLOG_USE_STD_FORMAT +#include +#if __cpp_lib_format >= 202207L +#include +#else +#include +#endif +#endif + +#ifdef SPDLOG_COMPILED_LIB +#undef SPDLOG_HEADER_ONLY +#if defined(SPDLOG_SHARED_LIB) +#if defined(_WIN32) +#ifdef spdlog_EXPORTS +#define SPDLOG_API __declspec(dllexport) +#else // !spdlog_EXPORTS +#define SPDLOG_API __declspec(dllimport) +#endif +#else // !defined(_WIN32) +#define SPDLOG_API __attribute__((visibility("default"))) +#endif +#else // !defined(SPDLOG_SHARED_LIB) +#define SPDLOG_API +#endif +#define SPDLOG_INLINE +#else // !defined(SPDLOG_COMPILED_LIB) +#define SPDLOG_API +#define SPDLOG_HEADER_ONLY +#define SPDLOG_INLINE inline +#endif // #ifdef SPDLOG_COMPILED_LIB + +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) && \ + FMT_VERSION >= 80000 // backward compatibility with fmt versions older than 8 +#define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string) +#define SPDLOG_FMT_STRING(format_string) FMT_STRING(format_string) +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +#include +#endif +#else +#define SPDLOG_FMT_RUNTIME(format_string) format_string +#define SPDLOG_FMT_STRING(format_string) format_string +#endif + +// visual studio up to 2013 does not support noexcept nor constexpr +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SPDLOG_NOEXCEPT _NOEXCEPT +#define SPDLOG_CONSTEXPR +#else +#define SPDLOG_NOEXCEPT noexcept +#define SPDLOG_CONSTEXPR constexpr +#endif + +// If building with std::format, can just use constexpr, otherwise if building with fmt +// SPDLOG_CONSTEXPR_FUNC needs to be set the same as FMT_CONSTEXPR to avoid situations where +// a constexpr function in spdlog could end up calling a non-constexpr function in fmt +// depending on the compiler +// If fmt determines it can't use constexpr, we should inline the function instead +#ifdef SPDLOG_USE_STD_FORMAT +#define SPDLOG_CONSTEXPR_FUNC constexpr +#else // Being built with fmt +#if FMT_USE_CONSTEXPR +#define SPDLOG_CONSTEXPR_FUNC FMT_CONSTEXPR +#else +#define SPDLOG_CONSTEXPR_FUNC inline +#endif +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPDLOG_DEPRECATED __declspec(deprecated) +#else +#define SPDLOG_DEPRECATED +#endif + +// disable thread local on msvc 2013 +#ifndef SPDLOG_NO_TLS +#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) +#define SPDLOG_NO_TLS 1 +#endif +#endif + +#ifndef SPDLOG_FUNCTION +#define SPDLOG_FUNCTION static_cast(__FUNCTION__) +#endif + +#ifdef SPDLOG_NO_EXCEPTIONS +#define SPDLOG_TRY +#define SPDLOG_THROW(ex) \ + do { \ + printf("spdlog fatal error: %s\n", ex.what()); \ + std::abort(); \ + } while (0) +#define SPDLOG_CATCH_STD +#else +#define SPDLOG_TRY try +#define SPDLOG_THROW(ex) throw(ex) +#define SPDLOG_CATCH_STD \ + catch (const std::exception &) { \ + } +#endif + +namespace spdlog { + +class formatter; + +namespace sinks { +class sink; +} + +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +using filename_t = std::wstring; +// 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 +using filename_t = std::string; +#define SPDLOG_FILENAME_T(s) s +#endif + +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr; +using sinks_init_list = std::initializer_list; +using err_handler = std::function; +#ifdef SPDLOG_USE_STD_FORMAT +namespace fmt_lib = std; + +using string_view_t = std::string_view; +using memory_buf_t = std::string; + +template +#if __cpp_lib_format >= 202207L +using format_string_t = std::format_string; +#else +using format_string_t = std::string_view; +#endif + +template +struct is_convertible_to_basic_format_string + : std::integral_constant>::value> {}; + +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +using wstring_view_t = std::wstring_view; +using wmemory_buf_t = std::wstring; + +template +#if __cpp_lib_format >= 202207L +using wformat_string_t = std::wformat_string; +#else +using wformat_string_t = std::wstring_view; +#endif +#endif +#define SPDLOG_BUF_TO_STRING(x) x +#else // use fmt lib instead of std::format +namespace fmt_lib = fmt; + +using string_view_t = fmt::basic_string_view; +using memory_buf_t = fmt::basic_memory_buffer; + +template +using format_string_t = fmt::format_string; + +template +using remove_cvref_t = typename std::remove_cv::type>::type; + +template +#if FMT_VERSION >= 90101 +using fmt_runtime_string = fmt::runtime_format_string; +#else +using fmt_runtime_string = fmt::basic_runtime; +#endif + +// clang doesn't like SFINAE disabled constructor in std::is_convertible<> so have to repeat the +// condition from basic_format_string here, in addition, fmt::basic_runtime is only +// convertible to basic_format_string but not basic_string_view +template +struct is_convertible_to_basic_format_string + : std::integral_constant>::value || + std::is_same, fmt_runtime_string>::value> { +}; + +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +using wstring_view_t = fmt::basic_string_view; +using wmemory_buf_t = fmt::basic_memory_buffer; + +template +using wformat_string_t = fmt::wformat_string; +#endif +#define SPDLOG_BUF_TO_STRING(x) fmt::to_string(x) +#endif + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +#ifndef _WIN32 +#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows +#endif // _WIN32 +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + +template +struct is_convertible_to_any_format_string + : std::integral_constant::value || + is_convertible_to_basic_format_string::value> {}; + +#if defined(SPDLOG_NO_ATOMIC_LEVELS) +using level_t = details::null_atomic_int; +#else +using level_t = std::atomic; +#endif + +#define SPDLOG_LEVEL_TRACE 0 +#define SPDLOG_LEVEL_DEBUG 1 +#define SPDLOG_LEVEL_INFO 2 +#define SPDLOG_LEVEL_WARN 3 +#define SPDLOG_LEVEL_ERROR 4 +#define SPDLOG_LEVEL_CRITICAL 5 +#define SPDLOG_LEVEL_OFF 6 + +#if !defined(SPDLOG_ACTIVE_LEVEL) +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO +#endif + +// Log level enum +namespace level { +enum level_enum : int { + trace = SPDLOG_LEVEL_TRACE, + debug = SPDLOG_LEVEL_DEBUG, + info = SPDLOG_LEVEL_INFO, + warn = SPDLOG_LEVEL_WARN, + err = SPDLOG_LEVEL_ERROR, + critical = SPDLOG_LEVEL_CRITICAL, + off = SPDLOG_LEVEL_OFF, + n_levels +}; + +#define SPDLOG_LEVEL_NAME_TRACE spdlog::string_view_t("trace", 5) +#define SPDLOG_LEVEL_NAME_DEBUG spdlog::string_view_t("debug", 5) +#define SPDLOG_LEVEL_NAME_INFO spdlog::string_view_t("info", 4) +#define SPDLOG_LEVEL_NAME_WARNING spdlog::string_view_t("warning", 7) +#define SPDLOG_LEVEL_NAME_ERROR spdlog::string_view_t("error", 5) +#define SPDLOG_LEVEL_NAME_CRITICAL spdlog::string_view_t("critical", 8) +#define SPDLOG_LEVEL_NAME_OFF spdlog::string_view_t("off", 3) + +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES \ + { \ + 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 + +#if !defined(SPDLOG_SHORT_LEVEL_NAMES) + +#define SPDLOG_SHORT_LEVEL_NAMES \ + { "T", "D", "I", "W", "E", "C", "O" } +#endif + +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 spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT; + +} // namespace level + +// +// Color mode used by sinks with color support. +// +enum class color_mode { always, automatic, never }; + +// +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default +// +enum class pattern_time_type { + local, // log localtime + utc // log utc +}; + +// +// Log exception +// +class SPDLOG_API spdlog_ex : public std::exception { +public: + explicit spdlog_ex(std::string msg); + spdlog_ex(const std::string &msg, int last_errno); + const char *what() const SPDLOG_NOEXCEPT override; + +private: + std::string msg_; +}; + +[[noreturn]] SPDLOG_API void throw_spdlog_ex(const std::string &msg, int last_errno); +[[noreturn]] SPDLOG_API void throw_spdlog_ex(std::string msg); + +struct source_loc { + SPDLOG_CONSTEXPR source_loc() = default; + SPDLOG_CONSTEXPR source_loc(const char *filename_in, int line_in, const char *funcname_in) + : filename{filename_in}, + line{line_in}, + funcname{funcname_in} {} + + SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT { return line <= 0; } + const char *filename{nullptr}; + int line{0}; + const char *funcname{nullptr}; +}; + +struct file_event_handlers { + file_event_handlers() + : before_open(nullptr), + after_open(nullptr), + before_close(nullptr), + after_close(nullptr) {} + + std::function before_open; + std::function after_open; + std::function before_close; + std::function after_close; +}; + +namespace details { + +// to_string_view + +SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(const memory_buf_t &buf) + SPDLOG_NOEXCEPT { + return spdlog::string_view_t{buf.data(), buf.size()}; +} + +SPDLOG_CONSTEXPR_FUNC spdlog::string_view_t to_string_view(spdlog::string_view_t str) + SPDLOG_NOEXCEPT { + return str; +} + +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(const wmemory_buf_t &buf) + SPDLOG_NOEXCEPT { + return spdlog::wstring_view_t{buf.data(), buf.size()}; +} + +SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view_t str) + SPDLOG_NOEXCEPT { + return str; +} +#endif + +#if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L +template +SPDLOG_CONSTEXPR_FUNC std::basic_string_view to_string_view( + std::basic_format_string fmt) SPDLOG_NOEXCEPT { + return fmt.get(); +} +#endif + +// make_unique support for pre c++14 +#if __cplusplus >= 201402L // C++14 and beyond +using std::enable_if_t; +using std::make_unique; +#else +template +using enable_if_t = typename std::enable_if::type; + +template +std::unique_ptr make_unique(Args &&...args) { + static_assert(!std::is_array::value, "arrays not supported"); + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324) +template ::value, int> = 0> +constexpr T conditional_static_cast(U value) { + return static_cast(value); +} + +template ::value, int> = 0> +constexpr T conditional_static_cast(U value) { + return value; +} + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "common-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/backtracer-inl.h b/lib/spdlog/include/spdlog/details/backtracer-inl.h new file mode 100644 index 0000000..baa06b6 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/backtracer-inl.h @@ -0,0 +1,63 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif +namespace spdlog { +namespace details { +SPDLOG_INLINE backtracer::backtracer(const backtracer &other) { + std::lock_guard lock(other.mutex_); + enabled_ = other.enabled(); + messages_ = other.messages_; +} + +SPDLOG_INLINE backtracer::backtracer(backtracer &&other) SPDLOG_NOEXCEPT { + std::lock_guard lock(other.mutex_); + enabled_ = other.enabled(); + messages_ = std::move(other.messages_); +} + +SPDLOG_INLINE backtracer &backtracer::operator=(backtracer other) { + std::lock_guard lock(mutex_); + enabled_ = other.enabled(); + messages_ = std::move(other.messages_); + return *this; +} + +SPDLOG_INLINE void backtracer::enable(size_t size) { + std::lock_guard lock{mutex_}; + enabled_.store(true, std::memory_order_relaxed); + messages_ = circular_q{size}; +} + +SPDLOG_INLINE void backtracer::disable() { + std::lock_guard lock{mutex_}; + enabled_.store(false, std::memory_order_relaxed); +} + +SPDLOG_INLINE bool backtracer::enabled() const { return enabled_.load(std::memory_order_relaxed); } + +SPDLOG_INLINE void backtracer::push_back(const log_msg &msg) { + std::lock_guard lock{mutex_}; + messages_.push_back(log_msg_buffer{msg}); +} + +SPDLOG_INLINE bool backtracer::empty() const { + std::lock_guard lock{mutex_}; + return messages_.empty(); +} + +// pop all items in the q and apply the given fun on each of them. +SPDLOG_INLINE void backtracer::foreach_pop(std::function fun) { + std::lock_guard lock{mutex_}; + while (!messages_.empty()) { + auto &front_msg = messages_.front(); + fun(front_msg); + messages_.pop_front(); + } +} +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/backtracer.h b/lib/spdlog/include/spdlog/details/backtracer.h new file mode 100644 index 0000000..f9eb4b4 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/backtracer.h @@ -0,0 +1,45 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +#include +#include +#include + +// Store log messages in circular buffer. +// Useful for storing debug data in case of error/warning happens. + +namespace spdlog { +namespace details { +class SPDLOG_API backtracer { + mutable std::mutex mutex_; + std::atomic enabled_{false}; + circular_q messages_; + +public: + backtracer() = default; + backtracer(const backtracer &other); + + backtracer(backtracer &&other) SPDLOG_NOEXCEPT; + backtracer &operator=(backtracer other); + + void enable(size_t size); + void disable(); + bool enabled() const; + void push_back(const log_msg &msg); + bool empty() const; + + // pop all items in the q and apply the given fun on each of them. + void foreach_pop(std::function fun); +}; + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "backtracer-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/circular_q.h b/lib/spdlog/include/spdlog/details/circular_q.h new file mode 100644 index 0000000..29e9d25 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/circular_q.h @@ -0,0 +1,115 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +// circular q view of std::vector. +#pragma once + +#include +#include + +#include "spdlog/common.h" + +namespace spdlog { +namespace details { +template +class circular_q { + size_t max_items_ = 0; + typename std::vector::size_type head_ = 0; + typename std::vector::size_type tail_ = 0; + size_t overrun_counter_ = 0; + std::vector v_; + +public: + using value_type = T; + + // empty ctor - create a disabled queue with no elements allocated at all + circular_q() = default; + + explicit circular_q(size_t max_items) + : max_items_(max_items + 1) // one item is reserved as marker for full q + , + v_(max_items_) {} + + circular_q(const circular_q &) = default; + circular_q &operator=(const circular_q &) = default; + + // move cannot be default, + // since we need to reset head_, tail_, etc to zero in the moved object + circular_q(circular_q &&other) SPDLOG_NOEXCEPT { copy_moveable(std::move(other)); } + + circular_q &operator=(circular_q &&other) SPDLOG_NOEXCEPT { + copy_moveable(std::move(other)); + return *this; + } + + // push back, overrun (oldest) item if no room left + void push_back(T &&item) { + if (max_items_ > 0) { + v_[tail_] = std::move(item); + tail_ = (tail_ + 1) % max_items_; + + if (tail_ == head_) // overrun last item if full + { + head_ = (head_ + 1) % max_items_; + ++overrun_counter_; + } + } + } + + // Return reference to the front item. + // If there are no elements in the container, the behavior is undefined. + const T &front() const { return v_[head_]; } + + T &front() { return v_[head_]; } + + // Return number of elements actually stored + size_t size() const { + if (tail_ >= head_) { + return tail_ - head_; + } else { + return max_items_ - (head_ - tail_); + } + } + + // Return const reference to item by index. + // If index is out of range 0…size()-1, the behavior is undefined. + const T &at(size_t i) const { + assert(i < size()); + return v_[(head_ + i) % max_items_]; + } + + // Pop item from front. + // If there are no elements in the container, the behavior is undefined. + void pop_front() { head_ = (head_ + 1) % max_items_; } + + bool empty() const { return tail_ == head_; } + + bool full() const { + // head is ahead of the tail by 1 + if (max_items_ > 0) { + return ((tail_ + 1) % max_items_) == head_; + } + return false; + } + + size_t overrun_counter() const { return overrun_counter_; } + + void reset_overrun_counter() { overrun_counter_ = 0; } + +private: + // copy from other&& and reset it to disabled state + void copy_moveable(circular_q &&other) SPDLOG_NOEXCEPT { + max_items_ = other.max_items_; + head_ = other.head_; + tail_ = other.tail_; + overrun_counter_ = other.overrun_counter_; + v_ = std::move(other.v_); + + // put &&other in disabled, but valid state + other.max_items_ = 0; + other.head_ = other.tail_ = 0; + other.overrun_counter_ = 0; + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/console_globals.h b/lib/spdlog/include/spdlog/details/console_globals.h new file mode 100644 index 0000000..9c55210 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/console_globals.h @@ -0,0 +1,28 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +namespace spdlog { +namespace details { + +struct console_mutex { + using mutex_t = std::mutex; + static mutex_t &mutex() { + static mutex_t s_mutex; + return s_mutex; + } +}; + +struct console_nullmutex { + using mutex_t = null_mutex; + static mutex_t &mutex() { + static mutex_t s_mutex; + return s_mutex; + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/file_helper-inl.h b/lib/spdlog/include/spdlog/details/file_helper-inl.h new file mode 100644 index 0000000..c7260ec --- /dev/null +++ b/lib/spdlog/include/spdlog/details/file_helper-inl.h @@ -0,0 +1,151 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers) + : event_handlers_(event_handlers) {} + +SPDLOG_INLINE file_helper::~file_helper() { close(); } + +SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) { + close(); + filename_ = fname; + + auto *mode = SPDLOG_FILENAME_T("ab"); + auto *trunc_mode = SPDLOG_FILENAME_T("wb"); + + if (event_handlers_.before_open) { + event_handlers_.before_open(filename_); + } + for (int tries = 0; tries < open_tries_; ++tries) { + // create containing folder if not exists already. + 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 (event_handlers_.after_open) { + event_handlers_.after_open(filename_, fd_); + } + return; + } + + details::os::sleep_for_millis(open_interval_); + } + + throw_spdlog_ex("Failed opening file " + os::filename_to_str(filename_) + " for writing", + errno); +} + +SPDLOG_INLINE void file_helper::reopen(bool truncate) { + if (filename_.empty()) { + throw_spdlog_ex("Failed re opening file - was not opened before"); + } + this->open(filename_, truncate); +} + +SPDLOG_INLINE void file_helper::flush() { + if (std::fflush(fd_) != 0) { + throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno); + } +} + +SPDLOG_INLINE void file_helper::sync() { + if (!os::fsync(fd_)) { + throw_spdlog_ex("Failed to fsync file " + os::filename_to_str(filename_), errno); + } +} + +SPDLOG_INLINE void file_helper::close() { + if (fd_ != nullptr) { + if (event_handlers_.before_close) { + event_handlers_.before_close(filename_, fd_); + } + + std::fclose(fd_); + fd_ = nullptr; + + if (event_handlers_.after_close) { + event_handlers_.after_close(filename_); + } + } +} + +SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) { + if (fd_ == nullptr) return; + size_t msg_size = buf.size(); + auto data = buf.data(); + + if (!details::os::fwrite_bytes(data, msg_size, fd_)) { + throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno); + } +} + +SPDLOG_INLINE size_t file_helper::size() const { + if (fd_ == nullptr) { + throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_)); + } + return os::filesize(fd_); +} + +SPDLOG_INLINE const filename_t &file_helper::filename() const { return filename_; } + +// +// return file path and its extension: +// +// "mylog.txt" => ("mylog", ".txt") +// "mylog" => ("mylog", "") +// "mylog." => ("mylog.", "") +// "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") +// +// the starting dot in filenames is ignored (hidden files): +// +// ".mylog" => (".mylog". "") +// "my_folder/.mylog" => ("my_folder/.mylog", "") +// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") +SPDLOG_INLINE std::tuple file_helper::split_by_extension( + const filename_t &fname) { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as + // extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) { + return std::make_tuple(fname, filename_t()); + } + + // treat cases like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.find_last_of(details::os::folder_seps_filename); + if (folder_index != filename_t::npos && folder_index >= ext_index - 1) { + return std::make_tuple(fname, filename_t()); + } + + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); +} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/file_helper.h b/lib/spdlog/include/spdlog/details/file_helper.h new file mode 100644 index 0000000..aba0be4 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/file_helper.h @@ -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 +#include + +namespace spdlog { +namespace details { + +// Helper class for file sinks. +// When failing to open a file, retry several times(5) with a delay interval(10 ms). +// Throw spdlog_ex exception on errors. + +class SPDLOG_API file_helper { +public: + file_helper() = default; + explicit file_helper(const file_event_handlers &event_handlers); + + file_helper(const file_helper &) = delete; + file_helper &operator=(const file_helper &) = delete; + ~file_helper(); + + void open(const filename_t &fname, bool truncate = false); + void reopen(bool truncate); + void flush(); + void sync(); + void close(); + void write(const memory_buf_t &buf); + size_t size() const; + const filename_t &filename() const; + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple split_by_extension(const filename_t &fname); + +private: + const int open_tries_ = 5; + const unsigned int open_interval_ = 10; + std::FILE *fd_{nullptr}; + filename_t filename_; + file_event_handlers event_handlers_; +}; +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "file_helper-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/fmt_helper.h b/lib/spdlog/include/spdlog/details/fmt_helper.h new file mode 100644 index 0000000..b629b89 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/fmt_helper.h @@ -0,0 +1,141 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +#pragma once + +#include +#include +#include +#include +#include + +#ifdef SPDLOG_USE_STD_FORMAT +#include +#include +#endif + +// Some fmt helpers to efficiently format and pad ints and strings +namespace spdlog { +namespace details { +namespace fmt_helper { + +inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) { + auto *buf_ptr = view.data(); + dest.append(buf_ptr, buf_ptr + view.size()); +} + +#ifdef SPDLOG_USE_STD_FORMAT +template +inline void append_int(T n, memory_buf_t &dest) { + // Buffer should be large enough to hold all digits (digits10 + 1) and a sign + SPDLOG_CONSTEXPR const auto BUF_SIZE = std::numeric_limits::digits10 + 2; + char buf[BUF_SIZE]; + + auto [ptr, ec] = std::to_chars(buf, buf + BUF_SIZE, n, 10); + if (ec == std::errc()) { + dest.append(buf, ptr); + } else { + throw_spdlog_ex("Failed to format int", static_cast(ec)); + } +} +#else +template +inline void append_int(T n, memory_buf_t &dest) { + fmt::format_int i(n); + dest.append(i.data(), i.data() + i.size()); +} +#endif + +template +SPDLOG_CONSTEXPR_FUNC unsigned int count_digits_fallback(T n) { + // taken from fmt: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/format.h#L899-L912 + unsigned int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} + +template +inline unsigned int count_digits(T n) { + using count_type = + typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; +#ifdef SPDLOG_USE_STD_FORMAT + return count_digits_fallback(static_cast(n)); +#else + return static_cast(fmt:: +// fmt 7.0.0 renamed the internal namespace to detail. +// See: https://github.com/fmtlib/fmt/issues/1538 +#if FMT_VERSION < 70000 + internal +#else + detail +#endif + ::count_digits(static_cast(n))); +#endif +} + +inline void pad2(int n, memory_buf_t &dest) { + if (n >= 0 && n < 100) // 0-99 + { + dest.push_back(static_cast('0' + n / 10)); + dest.push_back(static_cast('0' + n % 10)); + } else // unlikely, but just in case, let fmt deal with it + { + fmt_lib::format_to(std::back_inserter(dest), SPDLOG_FMT_STRING("{:02}"), n); + } +} + +template +inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) { + static_assert(std::is_unsigned::value, "pad_uint must get unsigned T"); + for (auto digits = count_digits(n); digits < width; digits++) { + dest.push_back('0'); + } + append_int(n, dest); +} + +template +inline void pad3(T n, memory_buf_t &dest) { + static_assert(std::is_unsigned::value, "pad3 must get unsigned T"); + if (n < 1000) { + dest.push_back(static_cast(n / 100 + '0')); + n = n % 100; + dest.push_back(static_cast((n / 10) + '0')); + dest.push_back(static_cast((n % 10) + '0')); + } else { + append_int(n, dest); + } +} + +template +inline void pad6(T n, memory_buf_t &dest) { + pad_uint(n, 6, dest); +} + +template +inline void pad9(T n, memory_buf_t &dest) { + pad_uint(n, 9, dest); +} + +// return fraction of a second of the given time_point. +// e.g. +// fraction(tp) -> will return the millis part of the second +template +inline ToDuration time_fraction(log_clock::time_point tp) { + using std::chrono::duration_cast; + using std::chrono::seconds; + auto duration = tp.time_since_epoch(); + auto secs = duration_cast(duration); + return duration_cast(duration) - duration_cast(secs); +} + +} // namespace fmt_helper +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/log_msg-inl.h b/lib/spdlog/include/spdlog/details/log_msg-inl.h new file mode 100644 index 0000000..3a23dcf --- /dev/null +++ b/lib/spdlog/include/spdlog/details/log_msg-inl.h @@ -0,0 +1,44 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include + +namespace spdlog { +namespace details { + +SPDLOG_INLINE log_msg::log_msg(spdlog::log_clock::time_point log_time, + spdlog::source_loc loc, + string_view_t a_logger_name, + spdlog::level::level_enum lvl, + spdlog::string_view_t msg) + : logger_name(a_logger_name), + level(lvl), + time(log_time) +#ifndef SPDLOG_NO_THREAD_ID + , + thread_id(os::thread_id()) +#endif + , + source(loc), + payload(msg) { +} + +SPDLOG_INLINE log_msg::log_msg(spdlog::source_loc loc, + string_view_t a_logger_name, + spdlog::level::level_enum lvl, + spdlog::string_view_t msg) + : log_msg(os::now(), loc, a_logger_name, lvl, msg) {} + +SPDLOG_INLINE log_msg::log_msg(string_view_t a_logger_name, + spdlog::level::level_enum lvl, + spdlog::string_view_t msg) + : log_msg(os::now(), source_loc{}, a_logger_name, lvl, msg) {} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/log_msg.h b/lib/spdlog/include/spdlog/details/log_msg.h new file mode 100644 index 0000000..64b4bf6 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/log_msg.h @@ -0,0 +1,40 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +namespace spdlog { +namespace details { +struct SPDLOG_API log_msg { + log_msg() = default; + log_msg(log_clock::time_point log_time, + 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(const log_msg &other) = default; + log_msg &operator=(const log_msg &other) = default; + + string_view_t logger_name; + level::level_enum level{level::off}; + log_clock::time_point time; + size_t thread_id{0}; + + // wrapping the formatted text with color (updated by pattern_formatter). + mutable size_t color_range_start{0}; + mutable size_t color_range_end{0}; + + source_loc source; + string_view_t payload; +}; +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "log_msg-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/log_msg_buffer-inl.h b/lib/spdlog/include/spdlog/details/log_msg_buffer-inl.h new file mode 100644 index 0000000..45c3383 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/log_msg_buffer-inl.h @@ -0,0 +1,54 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +namespace spdlog { +namespace details { + +SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg &orig_msg) + : log_msg{orig_msg} { + buffer.append(logger_name.begin(), logger_name.end()); + buffer.append(payload.begin(), payload.end()); + update_string_views(); +} + +SPDLOG_INLINE log_msg_buffer::log_msg_buffer(const log_msg_buffer &other) + : log_msg{other} { + buffer.append(logger_name.begin(), logger_name.end()); + buffer.append(payload.begin(), payload.end()); + update_string_views(); +} + +SPDLOG_INLINE log_msg_buffer::log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT + : log_msg{other}, + buffer{std::move(other.buffer)} { + update_string_views(); +} + +SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(const log_msg_buffer &other) { + log_msg::operator=(other); + buffer.clear(); + buffer.append(other.buffer.data(), other.buffer.data() + other.buffer.size()); + update_string_views(); + return *this; +} + +SPDLOG_INLINE log_msg_buffer &log_msg_buffer::operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT { + log_msg::operator=(other); + buffer = std::move(other.buffer); + update_string_views(); + return *this; +} + +SPDLOG_INLINE void log_msg_buffer::update_string_views() { + logger_name = string_view_t{buffer.data(), logger_name.size()}; + payload = string_view_t{buffer.data() + logger_name.size(), payload.size()}; +} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/log_msg_buffer.h b/lib/spdlog/include/spdlog/details/log_msg_buffer.h new file mode 100644 index 0000000..c926cfa --- /dev/null +++ b/lib/spdlog/include/spdlog/details/log_msg_buffer.h @@ -0,0 +1,32 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include + +namespace spdlog { +namespace details { + +// Extend log_msg with internal buffer to store its payload. +// This is needed since log_msg holds string_views that points to stack data. + +class SPDLOG_API log_msg_buffer : public log_msg { + memory_buf_t buffer; + void update_string_views(); + +public: + log_msg_buffer() = default; + explicit log_msg_buffer(const log_msg &orig_msg); + log_msg_buffer(const log_msg_buffer &other); + log_msg_buffer(log_msg_buffer &&other) SPDLOG_NOEXCEPT; + log_msg_buffer &operator=(const log_msg_buffer &other); + log_msg_buffer &operator=(log_msg_buffer &&other) SPDLOG_NOEXCEPT; +}; + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "log_msg_buffer-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/mpmc_blocking_q.h b/lib/spdlog/include/spdlog/details/mpmc_blocking_q.h new file mode 100644 index 0000000..f153f6c --- /dev/null +++ b/lib/spdlog/include/spdlog/details/mpmc_blocking_q.h @@ -0,0 +1,177 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// multi producer-multi consumer blocking queue. +// enqueue(..) - will block until room found to put the new message. +// enqueue_nowait(..) - enqueue immediately. overruns oldest message if no +// room left. +// dequeue_for(..) - will block until the queue is not empty or timeout have +// passed. + +#include + +#include +#include +#include + +namespace spdlog { +namespace details { + +template +class mpmc_blocking_queue { +public: + using item_type = T; + explicit mpmc_blocking_queue(size_t max_items) + : q_(max_items) {} + +#ifndef __MINGW32__ + // try to enqueue and block if no room left + void enqueue(T &&item) { + { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) { + { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + void enqueue_if_have_room(T &&item) { + bool pushed = false; + { + std::unique_lock lock(queue_mutex_); + if (!q_.full()) { + q_.push_back(std::move(item)); + pushed = true; + } + } + + if (pushed) { + push_cv_.notify_one(); + } else { + ++discard_counter_; + } + } + + // dequeue with a timeout. + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) { + { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) { + return false; + } + popped_item = std::move(q_.front()); + q_.pop_front(); + } + pop_cv_.notify_one(); + return true; + } + + // blocking dequeue without a timeout. + void dequeue(T &popped_item) { + { + std::unique_lock lock(queue_mutex_); + push_cv_.wait(lock, [this] { return !this->q_.empty(); }); + popped_item = std::move(q_.front()); + q_.pop_front(); + } + pop_cv_.notify_one(); + } + +#else + // apparently mingw deadlocks if the mutex is released before cv.notify_one(), + // so release the mutex at the very end each function. + + // try to enqueue and block if no room left + void enqueue(T &&item) { + std::unique_lock lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) { + std::unique_lock lock(queue_mutex_); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + void enqueue_if_have_room(T &&item) { + bool pushed = false; + std::unique_lock lock(queue_mutex_); + if (!q_.full()) { + q_.push_back(std::move(item)); + pushed = true; + } + + if (pushed) { + push_cv_.notify_one(); + } else { + ++discard_counter_; + } + } + + // dequeue with a timeout. + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) { + std::unique_lock lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) { + return false; + } + popped_item = std::move(q_.front()); + q_.pop_front(); + pop_cv_.notify_one(); + return true; + } + + // blocking dequeue without a timeout. + void dequeue(T &popped_item) { + std::unique_lock lock(queue_mutex_); + push_cv_.wait(lock, [this] { return !this->q_.empty(); }); + popped_item = std::move(q_.front()); + q_.pop_front(); + pop_cv_.notify_one(); + } + +#endif + + size_t overrun_counter() { + std::lock_guard lock(queue_mutex_); + return q_.overrun_counter(); + } + + size_t discard_counter() { return discard_counter_.load(std::memory_order_relaxed); } + + size_t size() { + std::lock_guard lock(queue_mutex_); + return q_.size(); + } + + void reset_overrun_counter() { + std::lock_guard lock(queue_mutex_); + q_.reset_overrun_counter(); + } + + void reset_discard_counter() { discard_counter_.store(0, std::memory_order_relaxed); } + +private: + std::mutex queue_mutex_; + std::condition_variable push_cv_; + std::condition_variable pop_cv_; + spdlog::details::circular_q q_; + std::atomic discard_counter_{0}; +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/null_mutex.h b/lib/spdlog/include/spdlog/details/null_mutex.h new file mode 100644 index 0000000..7b391c3 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/null_mutex.h @@ -0,0 +1,35 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +// null, no cost dummy "mutex" and dummy "atomic" int + +namespace spdlog { +namespace details { +struct null_mutex { + void lock() const {} + void unlock() const {} +}; + +struct null_atomic_int { + int value{0}; + null_atomic_int() = default; + + explicit null_atomic_int(int new_value) + : value(new_value) {} + + int load(std::memory_order = std::memory_order_relaxed) const { return value; } + + void store(int new_value, std::memory_order = std::memory_order_relaxed) { value = new_value; } + + int exchange(int new_value, std::memory_order = std::memory_order_relaxed) { + std::swap(new_value, value); + return new_value; // return value before the call + } +}; + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/os-inl.h b/lib/spdlog/include/spdlog/details/os-inl.h new file mode 100644 index 0000000..92dfe12 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/os-inl.h @@ -0,0 +1,570 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include // for _get_osfhandle, _isatty, _fileno +#include // for _get_pid + +#ifdef __MINGW32__ +#include +#endif + +#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES) +#include +#include +#endif + +#include // for _mkdir/_wmkdir + +#else // unix + +#include +#include + +#ifdef __linux__ +#include //Use gettid() syscall under linux to get thread id + +#elif defined(_AIX) +#include // for pthread_getthrds_np + +#elif defined(__DragonFly__) || defined(__FreeBSD__) +#include // for pthread_getthreadid_np + +#elif defined(__NetBSD__) +#include // for _lwp_self + +#elif defined(__sun) +#include // for thr_self +#endif + +#endif // unix + +#if defined __APPLE__ +#include +#endif + +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +namespace spdlog { +namespace details { +namespace os { + +SPDLOG_INLINE spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT { +#if defined __linux__ && defined SPDLOG_CLOCK_COARSE + timespec ts; + ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); + return std::chrono::time_point( + std::chrono::duration_cast( + std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); + +#else + return log_clock::now(); +#endif +} +SPDLOG_INLINE std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT { +#ifdef _WIN32 + std::tm tm; + ::localtime_s(&tm, &time_tt); +#else + std::tm tm; + ::localtime_r(&time_tt, &tm); +#endif + return tm; +} + +SPDLOG_INLINE std::tm localtime() SPDLOG_NOEXCEPT { + std::time_t now_t = ::time(nullptr); + return localtime(now_t); +} + +SPDLOG_INLINE std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT { +#ifdef _WIN32 + std::tm tm; + ::gmtime_s(&tm, &time_tt); +#else + std::tm tm; + ::gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +SPDLOG_INLINE std::tm gmtime() SPDLOG_NOEXCEPT { + std::time_t now_t = ::time(nullptr); + return gmtime(now_t); +} + +// fopen_s on non windows for writing +SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) { +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + *fp = ::_wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); +#else + *fp = ::_fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); +#endif +#if defined(SPDLOG_PREVENT_CHILD_FD) + if (*fp != nullptr) { + auto file_handle = reinterpret_cast(_get_osfhandle(::_fileno(*fp))); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) { + ::fclose(*fp); + *fp = nullptr; + } + } +#endif +#else // unix +#if defined(SPDLOG_PREVENT_CHILD_FD) + const int mode_flag = mode == SPDLOG_FILENAME_T("ab") ? O_APPEND : O_TRUNC; + const int fd = + ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644)); + if (fd == -1) { + return true; + } + *fp = ::fdopen(fd, mode.c_str()); + if (*fp == nullptr) { + ::close(fd); + } +#else + *fp = ::fopen((filename.c_str()), mode.c_str()); +#endif +#endif + + return *fp == nullptr; +} + +SPDLOG_INLINE int remove(const filename_t &filename) SPDLOG_NOEXCEPT { +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return ::_wremove(filename.c_str()); +#else + return std::remove(filename.c_str()); +#endif +} + +SPDLOG_INLINE int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT { + return path_exists(filename) ? remove(filename) : 0; +} + +SPDLOG_INLINE int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT { +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return ::_wrename(filename1.c_str(), filename2.c_str()); +#else + return std::rename(filename1.c_str(), filename2.c_str()); +#endif +} + +// Return true if path exists (file or directory) +SPDLOG_INLINE bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT { +#ifdef _WIN32 + struct _stat buffer; +#ifdef SPDLOG_WCHAR_FILENAMES + return (::_wstat(filename.c_str(), &buffer) == 0); +#else + return (::_stat(filename.c_str(), &buffer) == 0); +#endif +#else // common linux/unix all have the stat system call + struct stat buffer; + return (::stat(filename.c_str(), &buffer) == 0); +#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 +SPDLOG_INLINE size_t filesize(FILE *f) { + if (f == nullptr) { + throw_spdlog_ex("Failed getting file size. fd is null"); + } +#if defined(_WIN32) && !defined(__CYGWIN__) + int fd = ::_fileno(f); +#if defined(_WIN64) // 64 bits + __int64 ret = ::_filelengthi64(fd); + if (ret >= 0) { + return static_cast(ret); + } + +#else // windows 32 bits + long ret = ::_filelength(fd); + if (ret >= 0) { + return static_cast(ret); + } +#endif + +#else // unix +// OpenBSD and AIX doesn't compile with :: before the fileno(..) +#if defined(__OpenBSD__) || defined(_AIX) + int fd = fileno(f); +#else + int fd = ::fileno(f); +#endif +// 64 bits(but not in osx, linux/musl or cygwin, where fstat64 is deprecated) +#if ((defined(__linux__) && defined(__GLIBC__)) || defined(__sun) || defined(_AIX)) && \ + (defined(__LP64__) || defined(_LP64)) + struct stat64 st; + if (::fstat64(fd, &st) == 0) { + return static_cast(st.st_size); + } +#else // other unix or linux 32 bits or cygwin + struct stat st; + if (::fstat(fd, &st) == 0) { + return static_cast(st.st_size); + } +#endif +#endif + throw_spdlog_ex("Failed getting file size from fd", errno); + return 0; // will not be reached. +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#if !defined(SPDLOG_NO_TZ_OFFSET) +#ifdef _WIN32 +// Compare the timestamp as Local (mktime) vs UTC (_mkgmtime) to get the offset. +SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) { + std::tm local_tm = tm; // copy since mktime might adjust it (normalize dates, set tm_isdst) + std::time_t local_time_t = std::mktime(&local_tm); + if (local_time_t == -1) { + return 0; // fallback + } + + std::time_t utc_time_t = _mkgmtime(&local_tm); + if (utc_time_t == -1) { + return 0; // fallback + } + auto offset_seconds = utc_time_t - local_time_t; + return static_cast(offset_seconds / 60); +} +#else +// On unix simply use tm_gmtoff +SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) { + return static_cast(tm.tm_gmtoff / 60); +} +#endif // _WIN32 +#endif // SPDLOG_NO_TZ_OFFSET + +// Return current thread id as size_t +// It exists because the std::this_thread::get_id() is much slower(especially +// under VS 2013) +SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT { +#ifdef _WIN32 + return static_cast(::GetCurrentThreadId()); +#elif defined(__linux__) +#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +#define SYS_gettid __NR_gettid +#endif + return static_cast(::syscall(SYS_gettid)); +#elif defined(_AIX) + struct __pthrdsinfo buf; + int reg_size = 0; + pthread_t pt = pthread_self(); + int retval = pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_TID, &buf, sizeof(buf), NULL, ®_size); + int tid = (!retval) ? buf.__pi_tid : 0; + return static_cast(tid); +#elif defined(__DragonFly__) || defined(__FreeBSD__) + return static_cast(::pthread_getthreadid_np()); +#elif defined(__NetBSD__) + return static_cast(::_lwp_self()); +#elif defined(__OpenBSD__) + return static_cast(::getthrid()); +#elif defined(__sun) + return static_cast(::thr_self()); +#elif __APPLE__ + uint64_t tid; +// There is no pthread_threadid_np prior to Mac OS X 10.6, and it is not supported on any PPC, +// including 10.6.8 Rosetta. __POWERPC__ is Apple-specific define encompassing ppc and ppc64. +#ifdef MAC_OS_X_VERSION_MAX_ALLOWED + { +#if (MAC_OS_X_VERSION_MAX_ALLOWED < 1060) || defined(__POWERPC__) + tid = pthread_mach_thread_np(pthread_self()); +#elif MAC_OS_X_VERSION_MIN_REQUIRED < 1060 + if (&pthread_threadid_np) { + pthread_threadid_np(nullptr, &tid); + } else { + tid = pthread_mach_thread_np(pthread_self()); + } +#else + pthread_threadid_np(nullptr, &tid); +#endif + } +#else + pthread_threadid_np(nullptr, &tid); +#endif + return static_cast(tid); +#else // Default to standard C++11 (other Unix) + return static_cast(std::hash()(std::this_thread::get_id())); +#endif +} + +// Return current thread id as size_t (from thread local storage) +SPDLOG_INLINE size_t thread_id() SPDLOG_NOEXCEPT { +#if defined(SPDLOG_NO_TLS) + return _thread_id(); +#else // cache thread id in tls + static thread_local const size_t tid = _thread_id(); + return tid; +#endif +} + +// This is avoid msvc issue in sleep_for that happens if the clock changes. +// See https://github.com/gabime/spdlog/issues/609 +SPDLOG_INLINE void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT { +#if defined(_WIN32) + ::Sleep(milliseconds); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +#endif +} + +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) { + memory_buf_t buf; + wstr_to_utf8buf(filename, buf); + return SPDLOG_BUF_TO_STRING(buf); +} +#else +SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) { return filename; } +#endif + +SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT { +#ifdef _WIN32 + return conditional_static_cast(::GetCurrentProcessId()); +#else + return conditional_static_cast(::getpid()); +#endif +} + +// Determine if the terminal supports colors +// Based on: https://github.com/agauniyal/rang/ +SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT { +#ifdef _WIN32 + return true; +#else + + static const bool result = []() { + const char *env_colorterm_p = std::getenv("COLORTERM"); + if (env_colorterm_p != nullptr) { + return true; + } + + static constexpr std::array 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 std::any_of(terms.begin(), terms.end(), [&](const char *term) { + return std::strstr(env_term_p, term) != nullptr; + }); + }(); + + return result; +#endif +} + +// Determine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +SPDLOG_INLINE bool in_terminal(FILE *file) SPDLOG_NOEXCEPT { +#ifdef _WIN32 + return ::_isatty(_fileno(file)) != 0; +#else + return ::isatty(fileno(file)) != 0; +#endif +} + +#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) { + if (wstr.size() > static_cast((std::numeric_limits::max)()) / 4 - 1) { + throw_spdlog_ex("UTF-16 string is too big to be converted to UTF-8"); + } + + int wstr_size = static_cast(wstr.size()); + if (wstr_size == 0) { + target.resize(0); + return; + } + + int result_size = static_cast(target.capacity()); + if ((wstr_size + 1) * 4 > result_size) { + result_size = + ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, NULL, 0, NULL, NULL); + } + + if (result_size > 0) { + target.resize(result_size); + result_size = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr_size, target.data(), + result_size, NULL, NULL); + + if (result_size > 0) { + target.resize(result_size); + return; + } + } + + throw_spdlog_ex( + fmt_lib::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((std::numeric_limits::max)()) - 1) { + throw_spdlog_ex("UTF-8 string is too big to be converted to UTF-16"); + } + + int str_size = static_cast(str.size()); + if (str_size == 0) { + target.resize(0); + return; + } + + // find the size to allocate for the result buffer + int result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0); + + if (result_size > 0) { + target.resize(result_size); + result_size = + ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(), result_size); + if (result_size > 0) { + assert(result_size == static_cast(target.size())); + return; + } + } + + throw_spdlog_ex( + fmt_lib::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError())); +} +#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && + // defined(_WIN32) + +// return true on success +static SPDLOG_INLINE bool mkdir_(const filename_t &path) { +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + return ::_wmkdir(path.c_str()) == 0; +#else + return ::_mkdir(path.c_str()) == 0; +#endif +#else + return ::mkdir(path.c_str(), mode_t(0755)) == 0; +#endif +} + +// create the given directory - and all directories leading to it +// return true on success or if the directory already exists +SPDLOG_INLINE bool create_dir(const filename_t &path) { + if (path_exists(path)) { + return true; + } + + if (path.empty()) { + return false; + } + + size_t search_offset = 0; + do { + 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 + if (token_pos == filename_t::npos) { + token_pos = path.size(); + } + + auto subdir = path.substr(0, token_pos); +#ifdef _WIN32 + // if subdir is just a drive letter, add a slash e.g. "c:"=>"c:\", + // otherwise path_exists(subdir) returns false (issue #3079) + const bool is_drive = subdir.length() == 2 && subdir[1] == ':'; + if (is_drive) { + subdir += '\\'; + token_pos++; + } +#endif + + if (!subdir.empty() && !path_exists(subdir) && !mkdir_(subdir)) { + return false; // return error if failed creating dir + } + search_offset = token_pos + 1; + } while (search_offset < path.size()); + + return true; +} + +// Return directory name from given path or empty string +// "abc/file" => "abc" +// "abc/" => "abc" +// "abc" => "" +// "abc///" => "abc//" +SPDLOG_INLINE filename_t dir_name(const filename_t &path) { + auto pos = path.find_last_of(folder_seps_filename); + return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif // _MSC_VER +std::string SPDLOG_INLINE getenv(const char *field) { +#if defined(_MSC_VER) && defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_DESKTOP_APP) && \ + (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP) + return std::string{}; // not supported under uwp +#else + char *buf = std::getenv(field); + return buf ? buf : std::string{}; +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +// Do fsync by FILE handlerpointer +// Return true on success +SPDLOG_INLINE bool fsync(FILE *fp) { +#ifdef _WIN32 + return FlushFileBuffers(reinterpret_cast(_get_osfhandle(_fileno(fp)))) != 0; +#else + return ::fsync(fileno(fp)) == 0; +#endif +} + +// Do non-locking fwrite if possible by the os or use the regular locking fwrite +// Return true on success. +SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) { +#if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED) + return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes; +#elif defined(SPDLOG_FWRITE_UNLOCKED) + return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes; +#else + return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes; +#endif +} + +} // namespace os +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/os.h b/lib/spdlog/include/spdlog/details/os.h new file mode 100644 index 0000000..8bb8d14 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/os.h @@ -0,0 +1,127 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include // std::time_t +#include + +namespace spdlog { +namespace details { +namespace os { + +SPDLOG_API spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT; + +SPDLOG_API std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; + +SPDLOG_API std::tm localtime() SPDLOG_NOEXCEPT; + +SPDLOG_API std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; + +SPDLOG_API std::tm gmtime() SPDLOG_NOEXCEPT; + +// eol definition +#if !defined(SPDLOG_EOL) +#ifdef _WIN32 +#define SPDLOG_EOL "\r\n" +#else +#define SPDLOG_EOL "\n" +#endif +#endif + +SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; + +// folder separator +#if !defined(SPDLOG_FOLDER_SEPS) +#ifdef _WIN32 +#define SPDLOG_FOLDER_SEPS "\\/" +#else +#define SPDLOG_FOLDER_SEPS "/" +#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 +SPDLOG_API bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode); + +// Remove filename. return 0 on success +SPDLOG_API int remove(const filename_t &filename) SPDLOG_NOEXCEPT; + +// Remove file if exists. return 0 on success +// Note: Non atomic (might return failure to delete if concurrently deleted by other process/thread) +SPDLOG_API int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT; + +SPDLOG_API int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT; + +// Return if file exists. +SPDLOG_API bool path_exists(const filename_t &filename) SPDLOG_NOEXCEPT; + +// Return file size according to open FILE* object +SPDLOG_API size_t filesize(FILE *f); + +// Return utc offset in minutes or throw spdlog_ex on failure +SPDLOG_API int utc_minutes_offset(const std::tm &tm = details::os::localtime()); + +// Return current thread id as size_t +// It exists because the std::this_thread::get_id() is much slower(especially +// under VS 2013) +SPDLOG_API size_t _thread_id() SPDLOG_NOEXCEPT; + +// Return current thread id as size_t (from thread local storage) +SPDLOG_API size_t thread_id() SPDLOG_NOEXCEPT; + +// This is avoid msvc issue in sleep_for that happens if the clock changes. +// See https://github.com/gabime/spdlog/issues/609 +SPDLOG_API void sleep_for_millis(unsigned int milliseconds) SPDLOG_NOEXCEPT; + +SPDLOG_API std::string filename_to_str(const filename_t &filename); + +SPDLOG_API int pid() SPDLOG_NOEXCEPT; + +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +SPDLOG_API bool is_color_terminal() SPDLOG_NOEXCEPT; + +// Determine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +SPDLOG_API bool in_terminal(FILE *file) SPDLOG_NOEXCEPT; + +#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 utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target); +#endif + +// Return directory name from given path or empty string +// "abc/file" => "abc" +// "abc/" => "abc" +// "abc" => "" +// "abc///" => "abc//" +SPDLOG_API filename_t dir_name(const filename_t &path); + +// Create a dir from the given path. +// Return true if succeeded or if this dir already exists. +SPDLOG_API bool create_dir(const filename_t &path); + +// non thread safe, cross platform getenv/getenv_s +// return empty string if field not found +SPDLOG_API std::string getenv(const char *field); + +// Do fsync by FILE objectpointer. +// Return true on success. +SPDLOG_API bool fsync(FILE *fp); + +// Do non-locking fwrite if possible by the os or use the regular locking fwrite +// Return true on success. +SPDLOG_API bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp); + +} // namespace os +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "os-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/periodic_worker-inl.h b/lib/spdlog/include/spdlog/details/periodic_worker-inl.h new file mode 100644 index 0000000..c12e9e1 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/periodic_worker-inl.h @@ -0,0 +1,26 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +namespace spdlog { +namespace details { + +// stop the worker thread and join it +SPDLOG_INLINE periodic_worker::~periodic_worker() { + if (worker_thread_.joinable()) { + { + std::lock_guard lock(mutex_); + active_ = false; + } + cv_.notify_one(); + worker_thread_.join(); + } +} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/periodic_worker.h b/lib/spdlog/include/spdlog/details/periodic_worker.h new file mode 100644 index 0000000..2f59bb3 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/periodic_worker.h @@ -0,0 +1,58 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// periodic worker thread - periodically executes the given callback function. +// +// RAII over the owned thread: +// creates the thread on construction. +// stops and joins the thread on destruction (if the thread is executing a callback, wait for it +// to finish first). + +#include +#include +#include +#include +#include +namespace spdlog { +namespace details { + +class SPDLOG_API periodic_worker { +public: + template + periodic_worker(const std::function &callback_fun, + std::chrono::duration interval) { + active_ = (interval > std::chrono::duration::zero()); + if (!active_) { + return; + } + + worker_thread_ = std::thread([this, callback_fun, interval]() { + for (;;) { + std::unique_lock lock(this->mutex_); + if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) { + return; // active_ == false, so exit this thread + } + callback_fun(); + } + }); + } + std::thread &get_thread() { return worker_thread_; } + periodic_worker(const periodic_worker &) = delete; + periodic_worker &operator=(const periodic_worker &) = delete; + // stop the worker thread and join it + ~periodic_worker(); + +private: + bool active_; + std::thread worker_thread_; + std::mutex mutex_; + std::condition_variable cv_; +}; +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "periodic_worker-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/registry-inl.h b/lib/spdlog/include/spdlog/details/registry-inl.h new file mode 100644 index 0000000..c1af924 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/registry-inl.h @@ -0,0 +1,270 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include +#include +#include + +#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER +// support for the default stdout color logger +#ifdef _WIN32 +#include +#else +#include +#endif +#endif // SPDLOG_DISABLE_DEFAULT_LOGGER + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +SPDLOG_INLINE registry::registry() + : formatter_(new pattern_formatter()) { +#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER +// create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). +#ifdef _WIN32 + auto color_sink = std::make_shared(); +#else + auto color_sink = std::make_shared(); +#endif + + const char *default_logger_name = ""; + default_logger_ = std::make_shared(default_logger_name, std::move(color_sink)); + loggers_[default_logger_name] = default_logger_; + +#endif // SPDLOG_DISABLE_DEFAULT_LOGGER +} + +SPDLOG_INLINE registry::~registry() = default; + +SPDLOG_INLINE void registry::register_logger(std::shared_ptr new_logger) { + std::lock_guard lock(logger_map_mutex_); + register_logger_(std::move(new_logger)); +} + +SPDLOG_INLINE void registry::register_or_replace(std::shared_ptr new_logger) { + std::lock_guard lock(logger_map_mutex_); + register_or_replace_(std::move(new_logger)); +} + +SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr new_logger) { + std::lock_guard lock(logger_map_mutex_); + new_logger->set_formatter(formatter_->clone()); + + if (err_handler_) { + new_logger->set_error_handler(err_handler_); + } + + // 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_); + + if (backtrace_n_messages_ > 0) { + new_logger->enable_backtrace(backtrace_n_messages_); + } + + if (automatic_registration_) { + register_logger_(std::move(new_logger)); + } +} + +SPDLOG_INLINE std::shared_ptr registry::get(const std::string &logger_name) { + std::lock_guard lock(logger_map_mutex_); + auto found = loggers_.find(logger_name); + return found == loggers_.end() ? nullptr : found->second; +} + +SPDLOG_INLINE std::shared_ptr registry::default_logger() { + std::lock_guard lock(logger_map_mutex_); + return default_logger_; +} + +// Return raw ptr to the default logger. +// To be used directly by the spdlog default api (e.g. spdlog::info) +// This make the default API faster, but cannot be used concurrently with set_default_logger(). +// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. +SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); } + +// set default logger. +// the default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. +SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr new_default_logger) { + std::lock_guard lock(logger_map_mutex_); + if (new_default_logger != nullptr) { + loggers_[new_default_logger->name()] = new_default_logger; + } + default_logger_ = std::move(new_default_logger); +} + +SPDLOG_INLINE void registry::set_tp(std::shared_ptr tp) { + std::lock_guard lock(tp_mutex_); + tp_ = std::move(tp); +} + +SPDLOG_INLINE std::shared_ptr registry::get_tp() { + std::lock_guard lock(tp_mutex_); + return tp_; +} + +// Set global formatter. Each sink in each logger will get a clone of this object +SPDLOG_INLINE void registry::set_formatter(std::unique_ptr formatter) { + std::lock_guard lock(logger_map_mutex_); + formatter_ = std::move(formatter); + for (auto &l : loggers_) { + l.second->set_formatter(formatter_->clone()); + } +} + +SPDLOG_INLINE void registry::enable_backtrace(size_t n_messages) { + std::lock_guard lock(logger_map_mutex_); + backtrace_n_messages_ = n_messages; + + for (auto &l : loggers_) { + l.second->enable_backtrace(n_messages); + } +} + +SPDLOG_INLINE void registry::disable_backtrace() { + std::lock_guard lock(logger_map_mutex_); + backtrace_n_messages_ = 0; + for (auto &l : loggers_) { + l.second->disable_backtrace(); + } +} + +SPDLOG_INLINE void registry::set_level(level::level_enum log_level) { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) { + l.second->set_level(log_level); + } + global_log_level_ = log_level; +} + +SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) { + l.second->flush_on(log_level); + } + flush_level_ = log_level; +} + +SPDLOG_INLINE void registry::set_error_handler(err_handler handler) { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) { + l.second->set_error_handler(handler); + } + err_handler_ = std::move(handler); +} + +SPDLOG_INLINE void registry::apply_all( + const std::function)> &fun) { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) { + fun(l.second); + } +} + +SPDLOG_INLINE void registry::flush_all() { + std::lock_guard lock(logger_map_mutex_); + for (auto &l : loggers_) { + l.second->flush(); + } +} + +SPDLOG_INLINE void registry::drop(const std::string &logger_name) { + std::lock_guard lock(logger_map_mutex_); + auto is_default_logger = default_logger_ && default_logger_->name() == logger_name; + loggers_.erase(logger_name); + if (is_default_logger) { + default_logger_.reset(); + } +} + +SPDLOG_INLINE void registry::drop_all() { + std::lock_guard lock(logger_map_mutex_); + loggers_.clear(); + default_logger_.reset(); +} + +// clean all resources and threads started by the registry +SPDLOG_INLINE void registry::shutdown() { + { + std::lock_guard lock(flusher_mutex_); + periodic_flusher_.reset(); + } + + drop_all(); + + { + std::lock_guard lock(tp_mutex_); + tp_.reset(); + } +} + +SPDLOG_INLINE std::recursive_mutex ®istry::tp_mutex() { return tp_mutex_; } + +SPDLOG_INLINE void registry::set_automatic_registration(bool automatic_registration) { + std::lock_guard lock(logger_map_mutex_); + automatic_registration_ = automatic_registration; +} + +SPDLOG_INLINE void registry::set_levels(log_levels levels, level::level_enum *global_level) { + std::lock_guard lock(logger_map_mutex_); + log_levels_ = std::move(levels); + auto global_level_requested = global_level != nullptr; + global_log_level_ = global_level_requested ? *global_level : global_log_level_; + + for (auto &logger : loggers_) { + auto logger_entry = log_levels_.find(logger.first); + if (logger_entry != log_levels_.end()) { + logger.second->set_level(logger_entry->second); + } else if (global_level_requested) { + logger.second->set_level(*global_level); + } + } +} + +SPDLOG_INLINE registry ®istry::instance() { + static registry s_instance; + return s_instance; +} + +SPDLOG_INLINE void registry::apply_logger_env_levels(std::shared_ptr new_logger) { + std::lock_guard lock(logger_map_mutex_); + 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); +} + +SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) { + if (loggers_.find(logger_name) != loggers_.end()) { + throw_spdlog_ex("logger with name '" + logger_name + "' already exists"); + } +} + +SPDLOG_INLINE void registry::register_logger_(std::shared_ptr new_logger) { + const auto &logger_name = new_logger->name(); + throw_if_exists_(logger_name); + loggers_[logger_name] = std::move(new_logger); +} + +SPDLOG_INLINE void registry::register_or_replace_(std::shared_ptr new_logger) { + loggers_[new_logger->name()] = std::move(new_logger); +} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/registry.h b/lib/spdlog/include/spdlog/details/registry.h new file mode 100644 index 0000000..576803e --- /dev/null +++ b/lib/spdlog/include/spdlog/details/registry.h @@ -0,0 +1,131 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// Loggers registry of unique name->logger pointer +// An attempt to create a logger with an already existing name will result with spdlog_ex exception. +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +class logger; + +namespace details { +class thread_pool; + +class SPDLOG_API registry { +public: + using log_levels = std::unordered_map; + registry(const registry &) = delete; + registry &operator=(const registry &) = delete; + + void register_logger(std::shared_ptr new_logger); + void register_or_replace(std::shared_ptr new_logger); + void initialize_logger(std::shared_ptr new_logger); + std::shared_ptr get(const std::string &logger_name); + std::shared_ptr default_logger(); + + // Return raw ptr to the default logger. + // To be used directly by the spdlog default api (e.g. spdlog::info) + // This make the default API faster, but cannot be used concurrently with set_default_logger(). + // e.g do not call set_default_logger() from one thread while calling spdlog::info() from + // another. + logger *get_default_raw(); + + // set default logger and add it to the registry if not registered already. + // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. + // Note: Make sure to unregister it when no longer needed or before calling again with a new + // logger. + void set_default_logger(std::shared_ptr new_default_logger); + + void set_tp(std::shared_ptr tp); + + std::shared_ptr get_tp(); + + // Set global formatter. Each sink in each logger will get a clone of this object + void set_formatter(std::unique_ptr formatter); + + void enable_backtrace(size_t n_messages); + + void disable_backtrace(); + + void set_level(level::level_enum log_level); + + void flush_on(level::level_enum log_level); + + template + void flush_every(std::chrono::duration interval) { + std::lock_guard lock(flusher_mutex_); + auto clbk = [this]() { this->flush_all(); }; + periodic_flusher_ = details::make_unique(clbk, interval); + } + + std::unique_ptr &get_flusher() { + std::lock_guard lock(flusher_mutex_); + return periodic_flusher_; + } + + void set_error_handler(err_handler handler); + + void apply_all(const std::function)> &fun); + + void flush_all(); + + void drop(const std::string &logger_name); + + void drop_all(); + + // clean all resources and threads started by the registry + void shutdown(); + + std::recursive_mutex &tp_mutex(); + + void set_automatic_registration(bool automatic_registration); + + // 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(); + + void apply_logger_env_levels(std::shared_ptr new_logger); + +private: + registry(); + ~registry(); + + void throw_if_exists_(const std::string &logger_name); + void register_logger_(std::shared_ptr new_logger); + void register_or_replace_(std::shared_ptr new_logger); + bool set_level_from_cfg_(logger *logger); + std::mutex logger_map_mutex_, flusher_mutex_; + std::recursive_mutex tp_mutex_; + std::unordered_map> loggers_; + log_levels log_levels_; + std::unique_ptr formatter_; + spdlog::level::level_enum global_log_level_ = level::info; + level::level_enum flush_level_ = level::off; + err_handler err_handler_; + std::shared_ptr tp_; + std::unique_ptr periodic_flusher_; + std::shared_ptr default_logger_; + bool automatic_registration_ = true; + size_t backtrace_n_messages_ = 0; +}; + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "registry-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/synchronous_factory.h b/lib/spdlog/include/spdlog/details/synchronous_factory.h new file mode 100644 index 0000000..4bd5a51 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/synchronous_factory.h @@ -0,0 +1,22 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include "registry.h" + +namespace spdlog { + +// Default logger factory- creates synchronous loggers +class logger; + +struct synchronous_factory { + template + static std::shared_ptr create(std::string logger_name, SinkArgs &&...args) { + auto sink = std::make_shared(std::forward(args)...); + auto new_logger = std::make_shared(std::move(logger_name), std::move(sink)); + details::registry::instance().initialize_logger(new_logger); + return new_logger; + } +}; +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/tcp_client-windows.h b/lib/spdlog/include/spdlog/details/tcp_client-windows.h new file mode 100644 index 0000000..956f9f9 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/tcp_client-windows.h @@ -0,0 +1,217 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#define WIN32_LEAN_AND_MEAN +// tcp client helper +#include +#include + +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "Mswsock.lib") +#pragma comment(lib, "AdvApi32.lib") + +namespace spdlog { +namespace details { +class tcp_client { + SOCKET socket_ = INVALID_SOCKET; + + static void init_winsock_() { + WSADATA wsaData; + auto rv = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rv != 0) { + throw_winsock_error_("WSAStartup failed", ::WSAGetLastError()); + } + } + + static void throw_winsock_error_(const std::string &msg, int last_error) { + char buf[512]; + ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, + (sizeof(buf) / sizeof(char)), NULL); + + throw_spdlog_ex(fmt_lib::format("tcp_sink - {}: {}", msg, buf)); + } + +public: + tcp_client() { init_winsock_(); } + + ~tcp_client() { + close(); + ::WSACleanup(); + } + + bool is_connected() const { return socket_ != INVALID_SOCKET; } + + void close() { + ::closesocket(socket_); + socket_ = INVALID_SOCKET; + } + + SOCKET fd() const { return socket_; } + + int connect_socket_with_timeout(SOCKET sockfd, + const struct sockaddr *addr, + int addrlen, + const timeval &tv) { + // If no timeout requested, do a normal blocking connect. + if (tv.tv_sec == 0 && tv.tv_usec == 0) { + int rv = ::connect(sockfd, addr, addrlen); + if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) { + return 0; + } + return rv; + } + + // Switch to non‐blocking mode + u_long mode = 1UL; + if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + return SOCKET_ERROR; + } + + int rv = ::connect(sockfd, addr, addrlen); + int last_error = WSAGetLastError(); + if (rv == 0 || last_error == WSAEISCONN) { + mode = 0UL; + if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + return SOCKET_ERROR; + } + return 0; + } + if (last_error != WSAEWOULDBLOCK) { + // Real error + mode = 0UL; + if (::ioctlsocket(sockfd, FIONBIO, &mode)) { + return SOCKET_ERROR; + } + return SOCKET_ERROR; + } + + // Wait until socket is writable or timeout expires + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(sockfd, &wfds); + + rv = ::select(0, nullptr, &wfds, nullptr, const_cast(&tv)); + + // Restore blocking mode regardless of select result + mode = 0UL; + if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) { + return SOCKET_ERROR; + } + + if (rv == 0) { + WSASetLastError(WSAETIMEDOUT); + return SOCKET_ERROR; + } + if (rv == SOCKET_ERROR) { + return SOCKET_ERROR; + } + + int so_error = 0; + int len = sizeof(so_error); + if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast(&so_error), &len) == + SOCKET_ERROR) { + return SOCKET_ERROR; + } + if (so_error != 0 && so_error != WSAEISCONN) { + // connection failed + WSASetLastError(so_error); + return SOCKET_ERROR; + } + + return 0; // success + } + + // try to connect or throw on failure + void connect(const std::string &host, int port, int timeout_ms = 0) { + if (is_connected()) { + close(); + } + struct addrinfo hints {}; + ZeroMemory(&hints, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on + hints.ai_socktype = SOCK_STREAM; // TCP + hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value + hints.ai_protocol = 0; + + timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + auto port_str = std::to_string(port); + struct addrinfo *addrinfo_result; + auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); + int last_error = 0; + if (rv != 0) { + last_error = ::WSAGetLastError(); + WSACleanup(); + throw_winsock_error_("getaddrinfo failed", last_error); + } + + // Try each address until we successfully connect(2). + for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { + socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (socket_ == INVALID_SOCKET) { + last_error = ::WSAGetLastError(); + WSACleanup(); + continue; + } + if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) { + last_error = 0; + break; + } + last_error = WSAGetLastError(); + ::closesocket(socket_); + socket_ = INVALID_SOCKET; + } + ::freeaddrinfo(addrinfo_result); + if (socket_ == INVALID_SOCKET) { + WSACleanup(); + throw_winsock_error_("connect failed", last_error); + } + if (timeout_ms > 0) { + DWORD tv = static_cast(timeout_ms); + ::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)); + ::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv)); + } + + // set TCP_NODELAY + int enable_flag = 1; + ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&enable_flag), + sizeof(enable_flag)); + } + + // Send exactly n_bytes of the given data. + // On error close the connection and throw. + void send(const char *data, size_t n_bytes) { + size_t bytes_sent = 0; + while (bytes_sent < n_bytes) { + const int send_flags = 0; + auto write_result = + ::send(socket_, data + bytes_sent, (int)(n_bytes - bytes_sent), send_flags); + if (write_result == SOCKET_ERROR) { + int last_error = ::WSAGetLastError(); + close(); + throw_winsock_error_("send failed", last_error); + } + + if (write_result == 0) // (probably should not happen but in any case..) + { + break; + } + bytes_sent += static_cast(write_result); + } + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/tcp_client.h b/lib/spdlog/include/spdlog/details/tcp_client.h new file mode 100644 index 0000000..a6f9b80 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/tcp_client.h @@ -0,0 +1,203 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifdef _WIN32 +#error include tcp_client-windows.h instead +#endif + +// tcp client helper +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spdlog { +namespace details { +class tcp_client { + int socket_ = -1; + +public: + bool is_connected() const { return socket_ != -1; } + + void close() { + if (is_connected()) { + ::close(socket_); + socket_ = -1; + } + } + + int fd() const { return socket_; } + + ~tcp_client() { close(); } + + int connect_socket_with_timeout(int sockfd, + const struct sockaddr *addr, + socklen_t addrlen, + const timeval &tv) { + // Blocking connect if timeout is zero + if (tv.tv_sec == 0 && tv.tv_usec == 0) { + int rv = ::connect(sockfd, addr, addrlen); + if (rv < 0 && errno == EISCONN) { + // already connected, treat as success + return 0; + } + return rv; + } + + // Non-blocking path + int orig_flags = ::fcntl(sockfd, F_GETFL, 0); + if (orig_flags < 0) { + return -1; + } + if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) { + return -1; + } + + int rv = ::connect(sockfd, addr, addrlen); + if (rv == 0 || (rv < 0 && errno == EISCONN)) { + // immediate connect or already connected + ::fcntl(sockfd, F_SETFL, orig_flags); + return 0; + } + if (errno != EINPROGRESS) { + ::fcntl(sockfd, F_SETFL, orig_flags); + return -1; + } + + // wait for writability + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(sockfd, &wfds); + + struct timeval tv_copy = tv; + rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy); + if (rv <= 0) { + // timeout or error + ::fcntl(sockfd, F_SETFL, orig_flags); + if (rv == 0) errno = ETIMEDOUT; + return -1; + } + + // check socket error + int so_error = 0; + socklen_t len = sizeof(so_error); + if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) { + ::fcntl(sockfd, F_SETFL, orig_flags); + return -1; + } + ::fcntl(sockfd, F_SETFL, orig_flags); + if (so_error != 0 && so_error != EISCONN) { + errno = so_error; + return -1; + } + + return 0; + } + + // try to connect or throw on failure + void connect(const std::string &host, int port, int timeout_ms = 0) { + close(); + struct addrinfo hints {}; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; // To work with IPv4, IPv6, and so on + hints.ai_socktype = SOCK_STREAM; // TCP + hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value + hints.ai_protocol = 0; + + struct timeval tv; + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + auto port_str = std::to_string(port); + struct addrinfo *addrinfo_result; + auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); + if (rv != 0) { + throw_spdlog_ex(fmt_lib::format("::getaddrinfo failed: {}", gai_strerror(rv))); + } + + // Try each address until we successfully connect(2). + int last_errno = 0; + for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { +#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); + if (socket_ == -1) { + last_errno = errno; + continue; + } + ::fcntl(socket_, F_SETFD, FD_CLOEXEC); + if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) { + last_errno = 0; + break; + } + last_errno = errno; + ::close(socket_); + socket_ = -1; + } + ::freeaddrinfo(addrinfo_result); + if (socket_ == -1) { + throw_spdlog_ex("::connect failed", last_errno); + } + + if (timeout_ms > 0) { + // Set timeouts for send and recv + ::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv)); + ::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv)); + } + + // set TCP_NODELAY + int enable_flag = 1; + ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&enable_flag), + sizeof(enable_flag)); + + // prevent sigpipe on systems where MSG_NOSIGNAL is not available +#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) + ::setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast(&enable_flag), + sizeof(enable_flag)); +#endif + +#if !defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL) +#error "tcp_sink would raise SIGPIPE since neither SO_NOSIGPIPE nor MSG_NOSIGNAL are available" +#endif + } + + // Send exactly n_bytes of the given data. + // On error close the connection and throw. + void send(const char *data, size_t n_bytes) { + size_t bytes_sent = 0; + while (bytes_sent < n_bytes) { +#if defined(MSG_NOSIGNAL) + const int send_flags = MSG_NOSIGNAL; +#else + const int send_flags = 0; +#endif + auto write_result = + ::send(socket_, data + bytes_sent, n_bytes - bytes_sent, send_flags); + if (write_result < 0) { + close(); + throw_spdlog_ex("write(2) failed", errno); + } + + if (write_result == 0) // (probably should not happen but in any case..) + { + break; + } + bytes_sent += static_cast(write_result); + } + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/thread_pool-inl.h b/lib/spdlog/include/spdlog/details/thread_pool-inl.h new file mode 100644 index 0000000..3fefc67 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/thread_pool-inl.h @@ -0,0 +1,125 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace details { + +SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, + size_t threads_n, + std::function on_thread_start, + std::function on_thread_stop) + : q_(q_max_items) { + if (threads_n == 0 || threads_n > 1000) { + throw_spdlog_ex( + "spdlog::thread_pool(): invalid threads_n param (valid " + "range is 1-1000)"); + } + for (size_t i = 0; i < threads_n; i++) { + threads_.emplace_back([this, on_thread_start, on_thread_stop] { + on_thread_start(); + this->thread_pool::worker_loop_(); + on_thread_stop(); + }); + } +} + +SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, + size_t threads_n, + std::function on_thread_start) + : thread_pool(q_max_items, threads_n, std::move(on_thread_start), [] {}) {} + +SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) + : thread_pool(q_max_items, threads_n, [] {}, [] {}) {} + +// message all threads to terminate gracefully join them +SPDLOG_INLINE thread_pool::~thread_pool() { + SPDLOG_TRY { + for (size_t i = 0; i < threads_.size(); i++) { + post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); + } + + for (auto &t : threads_) { + t.join(); + } + } + 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) { + async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg); + post_async_msg_(std::move(async_m), overflow_policy); +} + +void SPDLOG_INLINE thread_pool::post_flush(async_logger_ptr &&worker_ptr, + async_overflow_policy overflow_policy) { + post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); +} + +size_t SPDLOG_INLINE thread_pool::overrun_counter() { return q_.overrun_counter(); } + +void SPDLOG_INLINE thread_pool::reset_overrun_counter() { q_.reset_overrun_counter(); } + +size_t SPDLOG_INLINE thread_pool::discard_counter() { return q_.discard_counter(); } + +void SPDLOG_INLINE thread_pool::reset_discard_counter() { q_.reset_discard_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) { + if (overflow_policy == async_overflow_policy::block) { + q_.enqueue(std::move(new_msg)); + } else if (overflow_policy == async_overflow_policy::overrun_oldest) { + q_.enqueue_nowait(std::move(new_msg)); + } else { + assert(overflow_policy == async_overflow_policy::discard_new); + q_.enqueue_if_have_room(std::move(new_msg)); + } +} + +void SPDLOG_INLINE thread_pool::worker_loop_() { + while (process_next_msg_()) { + } +} + +// process next message in the queue +// returns true if this thread should still be active (while no terminated msg was received) +bool SPDLOG_INLINE thread_pool::process_next_msg_() { + async_msg incoming_async_msg; + q_.dequeue(incoming_async_msg); + + switch (incoming_async_msg.msg_type) { + case async_msg_type::log: { + incoming_async_msg.worker_ptr->backend_sink_it_(incoming_async_msg); + return true; + } + case async_msg_type::flush: { + incoming_async_msg.worker_ptr->backend_flush_(); + return true; + } + + case async_msg_type::terminate: { + return false; + } + + default: { + assert(false); + } + } + + return true; +} + +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/thread_pool.h b/lib/spdlog/include/spdlog/details/thread_pool.h new file mode 100644 index 0000000..b9303cf --- /dev/null +++ b/lib/spdlog/include/spdlog/details/thread_pool.h @@ -0,0 +1,117 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace spdlog { +class async_logger; + +namespace details { + +using async_logger_ptr = std::shared_ptr; + +enum class async_msg_type { log, flush, terminate }; + +// Async msg to move to/from the queue +// Movable only. should never be copied +struct async_msg : log_msg_buffer { + async_msg_type msg_type{async_msg_type::log}; + async_logger_ptr worker_ptr; + + async_msg() = default; + ~async_msg() = default; + + // should only be moved in or out of the queue.. + async_msg(const async_msg &) = delete; + +// support for vs2013 move +#if defined(_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) + : log_msg_buffer(std::move(other)), + msg_type(other.msg_type), + worker_ptr(std::move(other.worker_ptr)) {} + + async_msg &operator=(async_msg &&other) { + *static_cast(this) = std::move(other); + msg_type = other.msg_type; + worker_ptr = std::move(other.worker_ptr); + return *this; + } +#else // (_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&) = default; + async_msg &operator=(async_msg &&) = default; +#endif + + // construct from log_msg with given type + async_msg(async_logger_ptr &&worker, async_msg_type the_type, const details::log_msg &m) + : log_msg_buffer{m}, + msg_type{the_type}, + worker_ptr{std::move(worker)} {} + + async_msg(async_logger_ptr &&worker, async_msg_type the_type) + : log_msg_buffer{}, + msg_type{the_type}, + worker_ptr{std::move(worker)} {} + + explicit async_msg(async_msg_type the_type) + : async_msg{nullptr, the_type} {} +}; + +class SPDLOG_API thread_pool { +public: + using item_type = async_msg; + using q_type = details::mpmc_blocking_queue; + + thread_pool(size_t q_max_items, + size_t threads_n, + std::function on_thread_start, + std::function on_thread_stop); + thread_pool(size_t q_max_items, size_t threads_n, std::function on_thread_start); + thread_pool(size_t q_max_items, size_t threads_n); + + // message all threads to terminate gracefully and join them + ~thread_pool(); + + thread_pool(const thread_pool &) = delete; + thread_pool &operator=(thread_pool &&) = delete; + + 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); + size_t overrun_counter(); + void reset_overrun_counter(); + size_t discard_counter(); + void reset_discard_counter(); + size_t queue_size(); + +private: + q_type q_; + + std::vector threads_; + + void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy); + void worker_loop_(); + + // process next message in the queue + // return true if this thread should still be active (while no terminate msg + // was received) + bool process_next_msg_(); +}; + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "thread_pool-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/details/udp_client-windows.h b/lib/spdlog/include/spdlog/details/udp_client-windows.h new file mode 100644 index 0000000..fd60f28 --- /dev/null +++ b/lib/spdlog/include/spdlog/details/udp_client-windows.h @@ -0,0 +1,98 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// Helper RAII over winsock udp client socket. +// Will throw on construction if socket creation failed. + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#pragma comment(lib, "Ws2_32.lib") +#pragma comment(lib, "Mswsock.lib") +#pragma comment(lib, "AdvApi32.lib") +#endif + +namespace spdlog { +namespace details { +class udp_client { + static constexpr int TX_BUFFER_SIZE = 1024 * 10; + SOCKET socket_ = INVALID_SOCKET; + sockaddr_in addr_ = {}; + + static void init_winsock_() { + WSADATA wsaData; + auto rv = ::WSAStartup(MAKEWORD(2, 2), &wsaData); + if (rv != 0) { + throw_winsock_error_("WSAStartup failed", ::WSAGetLastError()); + } + } + + static void throw_winsock_error_(const std::string &msg, int last_error) { + char buf[512]; + ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, + (sizeof(buf) / sizeof(char)), NULL); + + throw_spdlog_ex(fmt_lib::format("udp_sink - {}: {}", msg, buf)); + } + + void cleanup_() { + if (socket_ != INVALID_SOCKET) { + ::closesocket(socket_); + } + socket_ = INVALID_SOCKET; + ::WSACleanup(); + } + +public: + udp_client(const std::string &host, uint16_t port) { + init_winsock_(); + + addr_.sin_family = PF_INET; + addr_.sin_port = htons(port); + addr_.sin_addr.s_addr = INADDR_ANY; + if (InetPtonA(PF_INET, host.c_str(), &addr_.sin_addr.s_addr) != 1) { + int last_error = ::WSAGetLastError(); + ::WSACleanup(); + throw_winsock_error_("error: Invalid address!", last_error); + } + + socket_ = ::socket(PF_INET, SOCK_DGRAM, 0); + if (socket_ == INVALID_SOCKET) { + int last_error = ::WSAGetLastError(); + ::WSACleanup(); + throw_winsock_error_("error: Create Socket failed", last_error); + } + + int option_value = TX_BUFFER_SIZE; + if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast(&option_value), sizeof(option_value)) < 0) { + int last_error = ::WSAGetLastError(); + cleanup_(); + throw_winsock_error_("error: setsockopt(SO_SNDBUF) Failed!", last_error); + } + } + + ~udp_client() { cleanup_(); } + + SOCKET fd() const { return socket_; } + + void send(const char *data, size_t n_bytes) { + socklen_t tolen = sizeof(struct sockaddr); + if (::sendto(socket_, data, static_cast(n_bytes), 0, (struct sockaddr *)&addr_, + tolen) == -1) { + throw_spdlog_ex("sendto(2) failed", errno); + } + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/udp_client.h b/lib/spdlog/include/spdlog/details/udp_client.h new file mode 100644 index 0000000..9825fdb --- /dev/null +++ b/lib/spdlog/include/spdlog/details/udp_client.h @@ -0,0 +1,80 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// Helper RAII over unix udp client socket. +// Will throw on construction if the socket creation failed. + +#ifdef _WIN32 +#error "include udp_client-windows.h instead" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace spdlog { +namespace details { + +class udp_client { + static constexpr int TX_BUFFER_SIZE = 1024 * 10; + int socket_ = -1; + struct sockaddr_in sockAddr_; + + void cleanup_() { + if (socket_ != -1) { + ::close(socket_); + socket_ = -1; + } + } + +public: + udp_client(const std::string &host, uint16_t port) { + socket_ = ::socket(PF_INET, SOCK_DGRAM, 0); + if (socket_ < 0) { + throw_spdlog_ex("error: Create Socket Failed!"); + } + + int option_value = TX_BUFFER_SIZE; + if (::setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast(&option_value), sizeof(option_value)) < 0) { + cleanup_(); + throw_spdlog_ex("error: setsockopt(SO_SNDBUF) Failed!"); + } + + sockAddr_.sin_family = AF_INET; + sockAddr_.sin_port = htons(port); + + if (::inet_aton(host.c_str(), &sockAddr_.sin_addr) == 0) { + cleanup_(); + throw_spdlog_ex("error: Invalid address!"); + } + + ::memset(sockAddr_.sin_zero, 0x00, sizeof(sockAddr_.sin_zero)); + } + + ~udp_client() { cleanup_(); } + + int fd() const { return socket_; } + + // Send exactly n_bytes of the given data. + // On error close the connection and throw. + void send(const char *data, size_t n_bytes) { + socklen_t tolen = sizeof(sockAddr_); + if (::sendto(socket_, data, n_bytes, 0, reinterpret_cast(&sockAddr_), + tolen) == -1) { + throw_spdlog_ex("sendto(2) failed", errno); + } + } +}; +} // namespace details +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/details/windows_include.h b/lib/spdlog/include/spdlog/details/windows_include.h new file mode 100644 index 0000000..10e04fc --- /dev/null +++ b/lib/spdlog/include/spdlog/details/windows_include.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX // prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include diff --git a/lib/spdlog/include/spdlog/fmt/bin_to_hex.h b/lib/spdlog/include/spdlog/fmt/bin_to_hex.h new file mode 100644 index 0000000..57ef302 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bin_to_hex.h @@ -0,0 +1,224 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include + +#if defined(__has_include) +#if __has_include() +#include +#endif +#endif + +#if __cpp_lib_span >= 202002L +#include +#endif + +// +// Support for logging binary data as hex +// format flags, any combination of the following: +// {:X} - print in uppercase. +// {:s} - don't separate each byte with space. +// {:p} - don't print the position on each line start. +// {:n} - don't split the output to lines. +// {:a} - show ASCII if :n is not set + +// +// Examples: +// +// std::vector v(200, 0x0b); +// logger->info("Some buffer {}", spdlog::to_hex(v)); +// char buf[128]; +// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); +// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf), 16)); + +namespace spdlog { +namespace details { + +template +class dump_info { +public: + dump_info(It range_begin, It range_end, size_t size_per_line) + : begin_(range_begin), + end_(range_end), + size_per_line_(size_per_line) {} + + // do not use begin() and end() to avoid collision with fmt/ranges + It get_begin() const { return begin_; } + It get_end() const { return end_; } + size_t size_per_line() const { return size_per_line_; } + +private: + It begin_, end_; + size_t size_per_line_; +}; +} // namespace details + +// create a dump_info that wraps the given container +template +inline details::dump_info to_hex(const Container &container, + size_t size_per_line = 32) { + static_assert(sizeof(typename Container::value_type) == 1, + "sizeof(Container::value_type) != 1"); + using Iter = typename Container::const_iterator; + return details::dump_info(std::begin(container), std::end(container), size_per_line); +} + +#if __cpp_lib_span >= 202002L + +template +inline details::dump_info::iterator> to_hex( + const std::span &container, size_t size_per_line = 32) { + using Container = std::span; + static_assert(sizeof(typename Container::value_type) == 1, + "sizeof(Container::value_type) != 1"); + using Iter = typename Container::iterator; + return details::dump_info(std::begin(container), std::end(container), size_per_line); +} + +#endif + +// create dump_info from ranges +template +inline details::dump_info to_hex(const It range_begin, + const It range_end, + size_t size_per_line = 32) { + return details::dump_info(range_begin, range_end, size_per_line); +} + +} // namespace spdlog + +namespace +#ifdef SPDLOG_USE_STD_FORMAT + std +#else + fmt +#endif +{ + +template +struct formatter, char> { + char delimiter = ' '; + bool put_newlines = true; + bool put_delimiters = true; + bool use_uppercase = false; + bool put_positions = true; // position on start of each line + bool show_ascii = false; + + // parse the format string flags + template + SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + auto it = ctx.begin(); + while (it != ctx.end() && *it != '}') { + switch (*it) { + case 'X': + use_uppercase = true; + break; + case 's': + put_delimiters = false; + break; + case 'p': + put_positions = false; + break; + case 'n': + put_newlines = false; + show_ascii = false; + break; + case 'a': + if (put_newlines) { + show_ascii = true; + } + break; + } + + ++it; + } + return it; + } + + // format the given bytes range as hex + template + auto format(const spdlog::details::dump_info &the_range, + FormatContext &ctx) const -> decltype(ctx.out()) { + SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; + SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; + const char *hex_chars = use_uppercase ? hex_upper : hex_lower; + +#if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000 + auto inserter = ctx.begin(); +#else + auto inserter = ctx.out(); +#endif + + int size_per_line = static_cast(the_range.size_per_line()); + auto start_of_line = the_range.get_begin(); + for (auto i = the_range.get_begin(); i != the_range.get_end(); i++) { + auto ch = static_cast(*i); + + if (put_newlines && + (i == the_range.get_begin() || i - start_of_line >= size_per_line)) { + if (show_ascii && i != the_range.get_begin()) { + *inserter++ = delimiter; + *inserter++ = delimiter; + for (auto j = start_of_line; j < i; j++) { + auto pc = static_cast(*j); + *inserter++ = std::isprint(pc) ? static_cast(*j) : '.'; + } + } + + put_newline(inserter, static_cast(i - the_range.get_begin())); + + // put first byte without delimiter in front of it + *inserter++ = hex_chars[(ch >> 4) & 0x0f]; + *inserter++ = hex_chars[ch & 0x0f]; + start_of_line = i; + continue; + } + + if (put_delimiters && i != the_range.get_begin()) { + *inserter++ = delimiter; + } + + *inserter++ = hex_chars[(ch >> 4) & 0x0f]; + *inserter++ = hex_chars[ch & 0x0f]; + } + if (show_ascii) // add ascii to last line + { + if (the_range.get_end() - the_range.get_begin() > size_per_line) { + auto blank_num = size_per_line - (the_range.get_end() - start_of_line); + while (blank_num-- > 0) { + *inserter++ = delimiter; + *inserter++ = delimiter; + if (put_delimiters) { + *inserter++ = delimiter; + } + } + } + *inserter++ = delimiter; + *inserter++ = delimiter; + for (auto j = start_of_line; j != the_range.get_end(); j++) { + auto pc = static_cast(*j); + *inserter++ = std::isprint(pc) ? static_cast(*j) : '.'; + } + } + return inserter; + } + + // put newline(and position header) + template + void put_newline(It inserter, std::size_t pos) const { +#ifdef _WIN32 + *inserter++ = '\r'; +#endif + *inserter++ = '\n'; + + if (put_positions) { + spdlog::fmt_lib::format_to(inserter, SPDLOG_FMT_STRING("{:04X}: "), pos); + } + } +}; +} // namespace std diff --git a/lib/spdlog/include/spdlog/fmt/bundled/args.h b/lib/spdlog/include/spdlog/fmt/bundled/args.h new file mode 100644 index 0000000..5e5f40f --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/args.h @@ -0,0 +1,220 @@ +// Formatting library for C++ - dynamic argument lists +// +// 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_ + +#ifndef FMT_MODULE +# include // std::reference_wrapper +# include // std::unique_ptr +# include +#endif + +#include "format.h" // std_string_view + +FMT_BEGIN_NAMESPACE +namespace detail { + +template struct is_reference_wrapper : std::false_type {}; +template +struct is_reference_wrapper> : std::true_type {}; + +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { + return static_cast(v); +} + +// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC +// 2022 (v17.10.0). +// +// 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 node is made a fake template. +template struct node { + virtual ~node() = default; + std::unique_ptr> next; +}; + +class dynamic_arg_list { + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template auto push(const Arg& arg) -> const T& { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; +} // namespace detail + +/** + * A dynamic list of formatting arguments with storage. + * + * It can be implicitly converted into `fmt::basic_format_args` for passing + * into type-erased formatting functions such as `fmt::vformat`. + */ +FMT_EXPORT template class dynamic_format_arg_store { + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; + + enum { + value = !(detail::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) + }; + }; + + template + using stored_t = conditional_t< + std::is_convertible>::value && + !detail::is_reference_wrapper::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + std::vector> 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; + + auto data() const -> const basic_format_arg* { + return named_info_.empty() ? data_.data() : data_.data() + 1; + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(arg); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) + data_.insert(data_.begin(), basic_format_arg(nullptr, 0)); + data_.emplace_back(detail::unwrap(arg.value)); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0] = {named_info_.data(), named_info_.size()}; + guard.release(); + } + + public: + constexpr dynamic_format_arg_store() = default; + + operator basic_format_args() const { + return basic_format_args(data(), static_cast(data_.size()), + !named_info_.empty()); + } + + /** + * 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 store; + * store.push_back(42); + * store.push_back("abc"); + * store.push_back(1.5f); + * std::string result = fmt::vformat("{} and {} and {}", store); + */ + template void push_back(const T& arg) { + if (detail::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(detail::unwrap(arg)); + } + + /** + * Adds a reference to the argument into the dynamic store for later passing + * to a formatting function. + * + * **Example**: + * + * fmt::dynamic_format_arg_store 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" + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::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 + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(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_ = {}; + } + + /// Reserves space to store at least `new_cap` arguments including + /// `new_cap_named` named arguments. + 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); + } + + /// Returns the number of elements in the store. + auto size() const noexcept -> size_t { return data_.size(); } +}; + +FMT_END_NAMESPACE + +#endif // FMT_ARGS_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/base.h b/lib/spdlog/include/spdlog/fmt/bundled/base.h new file mode 100644 index 0000000..620456b --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/base.h @@ -0,0 +1,3010 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) +# define FMT_MODULE +#endif + +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // memcmp + +# include // std::enable_if +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 120100 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L +// GCC only allows constexpr member functions in non-literal types since 7.2: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#ifdef FMT_USE_CONSTEVAL +// Use the provided definition. +#elif !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_USE_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_USE_EXCEPTIONS 0 +#elif defined(__clang__) && !defined(__cpp_exceptions) +# define FMT_USE_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_USE_EXCEPTIONS 0 +#else +# define FMT_USE_EXCEPTIONS 1 +#endif +#if FMT_USE_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef FMT_NO_UNIQUE_ADDRESS +// Use the provided definition. +#elif FMT_CPLUSPLUS < 202002L +// Not supported. +#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifdef FMT_NODISCARD +// Use the provided definition. +#elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +#else +# define FMT_NODISCARD +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +// Detect pragmas. +#define FMT_PRAGMA_IMPL(x) _Pragma(#x) +#if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) +#else +# define FMT_PRAGMA_GCC(x) +#endif +#if FMT_CLANG_VERSION +# define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) +#else +# define FMT_PRAGMA_CLANG(x) +#endif +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_WARNING(...) +#endif + +// Enable minimal optimizations for more compact code in debug mode. +FMT_PRAGMA_GCC(push_options) +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) +FMT_PRAGMA_GCC(optimize("Og")) +# define FMT_GCC_OPTIMIZED +#endif +FMT_PRAGMA_CLANG(diagnostic push) +FMT_PRAGMA_GCC(diagnostic push) + +#ifdef FMT_ALWAYS_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. +#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED) +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v12 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#ifdef _WIN32 +# define FMT_WIN32 1 +#else +# define FMT_WIN32 0 +#endif + +#if !defined(FMT_HEADER_ONLY) && FMT_WIN32 +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_OPTIMIZE_SIZE +# define FMT_OPTIMIZE_SIZE 0 +#endif + +// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher +// per-call binary size by passing built-in types through the extension API. +#ifndef FMT_BUILTIN_TYPES +# define FMT_BUILTIN_TYPES 1 +#endif + +#define FMT_APPLY_VARIADIC(expr) \ + using unused = int[]; \ + (void)unused { 0, (expr, 0)... } + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; +template using decay_t = typename std::decay::type; +using nullptr_t = decltype(nullptr); + +#if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION +// A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +template constexpr auto min_of(T a, T b) -> T { + return a < b ? a : b; +} +template constexpr auto max_of(T a, T b) -> T { + return a > b ? a : b; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between clang 14 and libstdc++ consteval-based +// std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { + return val; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : ::fmt::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Use the provided definition. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +inline auto map(int128_opt x) -> int128_opt { return x; } +inline auto map(uint128_opt x) -> uint128_opt { return x; } +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +inline auto map(int128_opt) -> monostate { return {}; } +inline auto map(uint128_opt) -> monostate { return {}; } +#endif + +#ifdef FMT_USE_BITINT +// Use the provided definition. +#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__) +# define FMT_USE_BITINT 1 +#else +# define FMT_USE_BITINT 0 +#endif + +#if FMT_USE_BITINT +FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") +template using bitint = _BitInt(N); +template using ubitint = unsigned _BitInt(N); +#else +template struct bitint {}; +template struct ubitint {}; +#endif // FMT_USE_BITINT + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +template +using unsigned_char = conditional_t; + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::is_convertible().data()), + const typename T::value_type*> {}; + +// Check if the literal encoding is UTF-8. +enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; +enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +static_assert(!FMT_UNICODE || use_utf8, + "Unicode support requires compiling with /utf-8"); + +template constexpr auto narrow(T*) -> char* { return nullptr; } +constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* { + return s; +} + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int { + if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +namespace adl { +using namespace std; + +template +auto invoke_back_inserter() + -> decltype(back_inserter(std::declval())); +} // namespace adl + +template +struct is_back_insert_iterator : std::false_type {}; + +template +struct is_back_insert_iterator< + It, bool_constant()), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> + typename OutputIt::container_type& { + struct accessor : OutputIt { + FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Parsing-related public API and forward declarations. +FMT_BEGIN_EXPORT + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string view object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(nullptr_t) = delete; + + /// Constructs a string view object from a C string. +#if FMT_GCC_VERSION + FMT_ALWAYS_INLINE +#endif + FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { +#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION + if (std::is_same::value && !detail::is_constant_evaluated()) { + size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr. + return; + } +#endif + size_t len = 0; + while (*s++) ++len; + size_ = len; + } + + /// Constructs a string view from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + int result = + detail::compare(data_, other.data_, min_of(size_, other.size_)); + if (result != 0) return result; + return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view; + +template class basic_appender; +using appender = basic_appender; + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +class context; +template class generic_context; +template class parse_context; + +// Longer aliases for C++20 compatibility. +template using basic_format_parse_context = parse_context; +using format_parse_context = parse_context; +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = + conditional_t::value, context, + generic_context, Char>>; + +template class basic_format_arg; +template class basic_format_args; + +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// A formatter for objects of type T. +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +enum class align { none, left, right, center, numeric }; +enum class sign { none, minus, plus, space }; +enum class arg_id_kind { none, index, name }; + +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Data is arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + unsigned data_ = 1 << fill_size_shift; + static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, ""); + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | + (static_cast(size) << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(fmt::align a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> arg_id_kind { + return static_cast((data_ & width_mask) >> width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr auto dynamic() const -> bool { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(fmt::sign s) { + data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8) | + (static_cast(fill_data_[2]) << 16)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + fill_data_[2] = static_cast(uchar >> 16); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } + + FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { + set_fill_size(specs.fill_size()); + for (size_t i = 0; i < max_fill_size; ++i) + fill_data_[i] = specs.fill_data_[i]; + } +}; + +// Format specifiers for built-in and string types. +struct format_specs : basic_specs { + int width; + int precision; + + constexpr format_specs() : width(0), precision(-1) {} +}; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + */ +template class parse_context { + private: + basic_string_view fmt_; + int next_arg_id_; + + enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; + + FMT_CONSTEXPR void do_check_arg_id(int arg_id); + + public: + using char_type = Char; + using iterator = const Char*; + + constexpr explicit parse_context(basic_string_view fmt, + int next_arg_id = 0) + : fmt_(fmt), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return fmt_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + fmt_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +#ifndef FMT_USE_LOCALE +# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) +#endif + +// A type-erased reference to std::locale to avoid the heavy include. +class locale_ref { +#if FMT_USE_LOCALE + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + + template + locale_ref(const Locale& loc) : locale_(&loc) { + // Check if std::isalpha is found via ADL to reduce the chance of misuse. + detail::ignore_unused(isalpha('x', loc)); + } + + inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#endif // FMT_USE_LOCALE + + public: + template auto get() const -> Locale; +}; + +FMT_END_EXPORT + +namespace detail { + +// Specifies if `T` is a code unit type. +template struct is_code_unit : std::false_type {}; +template <> struct is_code_unit : std::true_type {}; +template <> struct is_code_unit : std::true_type {}; +template <> struct is_code_unit : std::true_type {}; +template <> struct is_code_unit : std::true_type {}; +#ifdef __cpp_char8_t +template <> struct is_code_unit : bool_constant {}; +#endif + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +constexpr auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const T& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +/// String's character (code unit) type. detail:: is intentional to prevent ADL. +template ()))> +using char_t = typename V::value_type; + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + +struct view {}; + +template +struct is_view : std::false_type {}; +template +struct is_view> : std::is_base_of {}; + +template struct named_arg; +template struct is_named_arg : std::false_type {}; +template struct is_static_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template struct named_arg : view { + const Char* name; + const T& value; + + named_arg(const Char* n, const T& v) : name(n), value(v) {} + static_assert(!is_named_arg::value, "nested named arguments"); +}; + +template constexpr auto count() -> int { return B ? 1 : 0; } +template constexpr auto count() -> int { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> int { + return count::value...>(); +} +template constexpr auto count_static_named_args() -> int { + return count::value...>(); +} + +template struct named_arg_info { + const Char* name; + int id; +}; + +// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13. +template +FMT_CONSTEXPR void check_for_duplicate(named_arg_info* named_args, + int named_arg_index, + basic_string_view arg_name) { + for (int i = 0; i < named_arg_index; ++i) { + if (named_args[i].name == arg_name) report_error("duplicate named arg"); + } +} + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + check_for_duplicate(named_args, named_arg_index, arg.name); + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, + int&) { + ++arg_index; +} +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, + int& arg_index, int& named_arg_index) { + check_for_duplicate(named_args, named_arg_index, T::name); + named_args[named_arg_index++] = {T::name, arg_index++}; +} + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template +using format_as_result = + remove_cvref_t()))>; +template +using format_as_member_result = + remove_cvref_t::format_as(std::declval()))>; + +template +struct use_format_as : std::false_type {}; +// format_as member is only used to avoid injection into the std namespace. +template +struct use_format_as_member : std::false_type {}; + +// Only map owning types because mapping views can be unsafe. +template +struct use_format_as< + T, bool_constant>::value>> + : std::true_type {}; +template +struct use_format_as_member< + T, bool_constant>::value>> + : std::true_type {}; + +template > +using use_formatter = + bool_constant<(std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_array::value) && + !has_to_string_view::value && !is_named_arg::value && + !use_format_as::value && !use_format_as_member::value>; + +template > +auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) + -> decltype(formatter().format(*p, *ctx), std::true_type()); +template auto has_formatter_impl(...) -> std::false_type; + +// T can be const-qualified to check if it is const-formattable. +template constexpr auto has_formatter() -> bool { + return decltype(has_formatter_impl(static_cast(nullptr)))::value; +} + +// Maps formatting argument types to natively supported types or user-defined +// types with formatters. Returns void on errors to be SFINAE-friendly. +template struct type_mapper { + static auto map(signed char) -> int; + static auto map(unsigned char) -> unsigned; + static auto map(short) -> int; + static auto map(unsigned short) -> unsigned; + static auto map(int) -> int; + static auto map(unsigned) -> unsigned; + static auto map(long) -> long_type; + static auto map(unsigned long) -> ulong_type; + static auto map(long long) -> long long; + static auto map(unsigned long long) -> unsigned long long; + static auto map(int128_opt) -> int128_opt; + static auto map(uint128_opt) -> uint128_opt; + static auto map(bool) -> bool; + + template + static auto map(bitint) -> conditional_t; + template + static auto map(ubitint) + -> conditional_t; + + template ::value)> + static auto map(T) -> conditional_t< + std::is_same::value || std::is_same::value, Char, void>; + + static auto map(float) -> float; + static auto map(double) -> double; + static auto map(long double) -> long double; + + static auto map(Char*) -> const Char*; + static auto map(const Char*) -> const Char*; + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + static auto map(const T&) -> conditional_t::value, + basic_string_view, void>; + + static auto map(void*) -> const void*; + static auto map(const void*) -> const void*; + static auto map(volatile void*) -> const void*; + static auto map(const volatile void*) -> const void*; + static auto map(nullptr_t) -> const void*; + template ::value || + std::is_member_pointer::value)> + static auto map(const T&) -> void; + + template ::value)> + static auto map(const T& x) -> decltype(map(format_as(x))); + template ::value)> + static auto map(const T& x) -> decltype(map(formatter::format_as(x))); + + template ::value)> + static auto map(T&) -> conditional_t(), T&, void>; + + template ::value)> + static auto map(const T& named_arg) -> decltype(map(named_arg.value)); +}; + +// detail:: is used to workaround a bug in MSVC 2017. +template +using mapped_t = decltype(detail::type_mapper::map(std::declval())); + +// A type constant after applying type_mapper. +template +using mapped_type_constant = type_constant, Char>; + +template ::value> +using stored_type_constant = std::integral_constant< + type, Context::builtin_types || TYPE == type::int_type ? TYPE + : type::custom_type>; +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public parse_context { + private: + int num_args_; + const type* types_; + using base = parse_context; + + public: + FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, + int num_args, const type* types, + int next_arg_id = 0) + : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +// An argument reference. +template union arg_ref { + FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} + FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align { + switch (c) { + case '<': return align::left; + case '>': return align::right; + case '^': return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template struct dynamic_spec_handler { + parse_context& ctx; + arg_ref& ref; + arg_id_kind& kind; + + FMT_CONSTEXPR void on_index(int id) { + ref = id; + kind = arg_id_kind::index; + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = id; + kind = arg_id_kind::name; + ctx.check_arg_id(id); + } +}; + +template struct parse_dynamic_spec_result { + const Char* end; + arg_id_kind kind; +}; + +// Parses integer | "{" [arg_id] "}". +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + parse_context& ctx) + -> parse_dynamic_spec_result { + FMT_ASSERT(begin != end, ""); + auto kind = arg_id_kind::none; + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val == -1) report_error("number is too big"); + value = val; + } else { + if (*begin == '{') { + ++begin; + if (begin != end) { + Char c = *begin; + if (c == '}' || c == ':') { + int id = ctx.next_arg_id(); + ref = id; + kind = arg_id_kind::index; + ctx.check_dynamic_spec(id); + } else { + begin = parse_arg_id(begin, end, + dynamic_spec_handler{ctx, ref, kind}); + } + } + if (begin != end && *begin == '}') return {++begin, kind}; + } + report_error("invalid format string"); + } + return {begin, kind}; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + format_specs& specs, arg_ref& width_ref, + parse_context& ctx) -> const Char* { + auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + specs.set_dynamic_width(result.kind); + return result.end; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + format_specs& specs, + arg_ref& precision_ref, + parse_context& ctx) -> const Char* { + ++begin; + if (begin == end) { + report_error("invalid precision"); + return begin; + } + auto result = + parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); + specs.set_dynamic_precision(result.kind); + return result.end; +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + parse_context& ctx, type arg_type) + -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) report_error("invalid format specifier"); + specs.set_type(pres_type); + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.set_align(parse_align(c)); + ++begin; + break; + case '+': + case ' ': + specs.set_sign(c == ' ' ? sign::space : sign::plus); + FMT_FALLTHROUGH; + case '-': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.set_alt(); + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + report_error("format specifier requires numeric argument"); + if (specs.align() == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.set_align(align::numeric); + specs.set_fill('0'); + } + ++begin; + break; + // clang-format off + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '{': + // clang-format on + enter_state(state::width); + begin = parse_width(begin, end, specs, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.set_localized(); + ++begin; + break; + case 'd': return parse_presentation_type(pres::dec, integral_set); + case 'X': specs.set_upper(); FMT_FALLTHROUGH; + case 'x': return parse_presentation_type(pres::hex, integral_set); + case 'o': return parse_presentation_type(pres::oct, integral_set); + case 'B': specs.set_upper(); FMT_FALLTHROUGH; + case 'b': return parse_presentation_type(pres::bin, integral_set); + case 'E': specs.set_upper(); FMT_FALLTHROUGH; + case 'e': return parse_presentation_type(pres::exp, float_set); + case 'F': specs.set_upper(); FMT_FALLTHROUGH; + case 'f': return parse_presentation_type(pres::fixed, float_set); + case 'G': specs.set_upper(); FMT_FALLTHROUGH; + case 'g': return parse_presentation_type(pres::general, float_set); + case 'A': specs.set_upper(); FMT_FALLTHROUGH; + case 'a': return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto alignment = parse_align(to_ascii(*fill_end)); + enter_state(state::align, alignment != align::none); + specs.set_fill( + basic_string_view(begin, to_unsigned(fill_end - begin))); + specs.set_align(alignment); + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, + const Char* end, + Handler&& handler) + -> const Char* { + ++begin; + if (begin == end) { + handler.on_error("invalid format string"); + return end; + } + int arg_id = 0; + switch (*begin) { + case '}': + handler.on_replacement_field(handler.on_arg_id(), begin); + return begin + 1; + case '{': handler.on_text(begin, begin + 1); return begin + 1; + case ':': arg_id = handler.on_arg_id(); break; + default: { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + } adapter = {handler, 0}; + begin = parse_arg_id(begin, end, adapter); + arg_id = adapter.arg_id; + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(arg_id, begin); + return begin + 1; + } + if (c != ':') { + handler.on_error("missing '}' in format string"); + return end; + } + break; + } + } + begin = handler.on_format_specs(arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, + Handler&& handler) { + auto begin = fmt.data(), end = begin + fmt.size(); + auto p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + auto type = specs.type(); + if (type != presentation_type::none && type != presentation_type::chr && + type != presentation_type::debug) { + return false; + } + if (specs.align() == align::numeric || specs.sign() != sign::none || + specs.alt()) { + report_error("invalid format specifier for char"); + } + return true; +} + +// A base class for compile-time strings. +struct compile_string {}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { + using mapped_type = remove_cvref_t>; + constexpr bool formattable = + std::is_constructible>::value; + if (!formattable) return ctx.begin(); // Error is reported in the value ctor. + using formatted_type = conditional_t; + return formatter().parse(ctx); +} + +template struct arg_pack {}; + +template +class format_string_checker { + private: + type types_[max_of(1, NUM_ARGS)]; + named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; + compile_parse_context context_; + + using parse_func = auto (*)(parse_context&) -> const Char*; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; + + public: + template + FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, + arg_pack) + : types_{mapped_type_constant::value...}, + named_args_{}, + context_(fmt, NUM_ARGS, types_), + parse_funcs_{&invoke_parse...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_static_named_arg(named_args_, arg_index, named_arg_index)); + ignore_unused(arg_index, named_arg_index); + } + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + context_.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + for (int i = 0; i < NUM_NAMED_ARGS; ++i) { + if (named_args_[i].name == id) return named_args_[i].id; + } + if (!DYNAMIC_NAMES) on_error("argument not found"); + return -1; + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + context_.advance_to(begin); + if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); + + // If id is out of range, it means we do not know the type and cannot parse + // the format at compile time. Instead, skip over content until we finish + // the format spec, accounting for any nested replacements. + for (int bracket_count = 0; + begin != end && (bracket_count > 0 || *begin != '}'); ++begin) { + if (*begin == '{') + ++bracket_count; + else if (*begin == '}') + --bracket_count; + } + return begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + FMT_CONSTEXPR void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = min_of(count, capacity_); + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template +// Workaround for MSVC2019 to fix error C2893: Failed to specialize function +// template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 + FMT_CONSTEXPR20 +#endif + void + append(const U* begin, const U* end) { + while (begin != end) { + auto size = size_; + auto free_cap = capacity_ - size; + auto count = to_unsigned(end - begin); + if (free_cap < count) { + grow_(*this, size + count); + size = size_; + free_cap = capacity_ - size; + count = count < free_cap ? count : free_cap; + } + // A loop is faster than memcpy on small sizes. + T* out = ptr_ + size; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + constexpr explicit buffer_traits(size_t) {} + constexpr auto count() const -> size_t { return 0; } + constexpr auto limit(size_t size) const -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + constexpr auto count() const -> size_t { return count_; } + FMT_CONSTEXPR auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return min_of(size, n); + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +template +class container_buffer : public buffer { + private: + using value_type = typename Container::value_type; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container.resize(capacity); + self.set(&self.container[0], capacity); + } + + public: + Container& container; + + explicit container_buffer(Container& c) + : buffer(grow, c.size()), container(c) {} +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public container_buffer { + private: + using base = container_buffer; + + public: + explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : base(get_container(out)) {} + + auto out() -> OutputIt { return OutputIt(this->container); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + constexpr auto count() const noexcept -> size_t { + return count_ + this->size(); + } +}; + +template +struct is_back_insert_iterator> : std::true_type {}; + +template +struct has_back_insert_iterator_container_append : std::false_type {}; +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + +template +struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; + +template +struct has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt, + void_t()) + .insert(get_container(std::declval()).end(), + std::declval(), + std::declval()))>> : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template ::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value && + has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template ::value && + (has_back_insert_iterator_container_append< + OutputIt, InputIt>::value || + has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt>::value)))> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +struct is_buffer_appender : std::false_type {}; +template +struct is_buffer_appender< + It, bool_constant< + is_back_insert_iterator::value && + std::is_base_of, + typename It::container_type>::value>> + : std::true_type {}; + +// Maps an output iterator to a buffer. +template ::value)> +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template ::value)> +auto get_buffer(OutputIt out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +// This type is intentionally undefined, only used for errors. +template struct type_is_unformattable_for; + +template struct string_value { + const Char* data; + size_t size; + auto str() const -> basic_string_view { return {data, size}; } +}; + +template struct custom_value { + using char_type = typename Context::char_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +struct custom_tag {}; + +#if !FMT_BUILTIN_TYPES +# define FMT_BUILTIN , monostate +#else +# define FMT_BUILTIN +#endif + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(signed char x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(signed short x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(int x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} + FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} + FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) + : value(ulong_type(x)) {} + constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} + constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) + : ulong_long_value(x) {} + FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} + FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} + constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} + + template + constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + template + constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + + template ::value)> + constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { + static_assert( + std::is_same::value || std::is_same::value, + "mixing character types is disallowed"); + } + + constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} + constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} + FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} + + FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { + static_assert(std::is_same::value, + "mixing character types is disallowed"); + auto sv = to_string_view(x); + string.data = sv.data(); + string.size = sv.size(); + } + FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(const volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(nullptr_t) : pointer(nullptr) {} + + template ::value || + std::is_member_pointer::value)> + value(const T&) { + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + static_assert(sizeof(T) == 0, + "formatting of non-void pointers is disallowed"); + } + + template ::value)> + value(const T& x) : value(format_as(x)) {} + template ::value)> + value(const T& x) : value(formatter::format_as(x)) {} + + template ::value)> + value(const T& named_arg) : value(named_arg.value) {} + + template ::value || !FMT_BUILTIN_TYPES)> + FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} + + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + private: + template ())> + FMT_CONSTEXPR value(T& x, custom_tag) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. + if (!is_constant_evaluated()) { + custom.value = + const_cast(&reinterpret_cast(x)); + } else { + custom.value = nullptr; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same*>::value) + custom.value = const_cast(&x); +#endif + } + custom.format = format_custom; + } + + template ())> + FMT_CONSTEXPR value(const T&, custom_tag) { + // Cannot format an argument; to make type T formattable provide a + // formatter specialization: https://fmt.dev/latest/api.html#udt. + type_is_unformattable_for _; + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom(void* arg, parse_context& parse_ctx, + Context& ctx) { + auto f = formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, + enable_if_t&>()++), + T>::value>> : std::true_type {}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(stored_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr auto make_descriptor() -> unsigned long long { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template +struct named_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + arg_t args[1u + NUM_ARGS]; + named_arg_info + named_args[static_cast(NUM_NAMED_ARGS)]; + + template + FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, values...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_named_arg(named_args, arg_index, named_arg_index, values)); + } + + named_arg_store(named_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) + args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + named_arg_store(const named_arg_store& rhs) = delete; + auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete; + auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete; + operator const arg_t*() const { return args + 1; } +}; + +// An array of references to arguments. It can be implicitly converted to +// `basic_format_args` for passing into type-erased formatting functions +// such as `vformat`. It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + using type = + conditional_t[max_of(1, NUM_ARGS)], + named_arg_store>; + type args; +}; + +// TYPE can be different from type_constant, e.g. for __float128. +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.set_type(set ? presentation_type::debug : presentation_type::none); + } + + FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +template +struct locking + : bool_constant::value == type::custom_type> {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc = {}); + +#if FMT_WIN32 +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); +#else // format_args is passed by reference since it is defined later. +inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} +#endif +} // namespace detail + +// The main public API. + +template +FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { + // Argument id is only checked at compile time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && use_constexpr_cast) { + auto ctx = static_cast*>(this); + if (arg_id >= ctx->num_args()) report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { + using detail::compile_parse_context; + if (detail::is_constant_evaluated() && use_constexpr_cast) + static_cast*>(this)->check_dynamic_spec(arg_id); +} + +FMT_BEGIN_EXPORT + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + protected: + detail::buffer* container; + + public: + using container_type = detail::buffer; + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} + + FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { + container->push_back(c); + return *this; + } + FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } +}; + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + friend class basic_format_args; + + using char_type = typename Context::char_type; + + public: + class handle { + private: + detail::custom_value custom_; + + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(parse_context& parse_ctx, Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + template + basic_format_arg(T&& val) + : value_(val), type_(detail::stored_type_constant::value) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + auto type() const -> detail::type { return type_; } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { + using detail::map; + switch (type_) { + case detail::type::none_type: break; + case detail::type::int_type: return vis(value_.int_value); + case detail::type::uint_type: return vis(value_.uint_value); + case detail::type::long_long_type: return vis(value_.long_long_value); + case detail::type::ulong_long_type: return vis(value_.ulong_long_value); + case detail::type::int128_type: return vis(map(value_.int128_value)); + case detail::type::uint128_type: return vis(map(value_.uint128_value)); + case detail::type::bool_type: return vis(value_.bool_value); + case detail::type::char_type: return vis(value_.char_value); + case detail::type::float_type: return vis(value_.float_value); + case detail::type::double_type: return vis(value_.double_value); + case detail::type::long_double_type: return vis(value_.long_double_value); + case detail::type::cstring_type: return vis(value_.string.data); + case detail::type::string_type: return vis(value_.string.str()); + case detail::type::pointer_type: return vis(value_.pointer); + case detail::type::custom_type: return vis(handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + parse_context& parse_ctx, Context& ctx) + -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const basic_format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + template + using store = + detail::format_arg_store; + + public: + using format_arg = basic_format_arg; + + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + values_(s.args) {} + + template detail::max_packed_args)> + constexpr basic_format_args(const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + args_(s.args) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count, + bool has_named = false) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | + (has_named ? +detail::has_named_args_bit : 0)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + auto arg = format_arg(); + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + format_args args_; + FMT_NO_UNIQUE_ADDRESS locale_ref loc_; + + public: + using char_type = char; ///< The character type for the output. + using iterator = appender; + using format_arg = basic_format_arg; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + /// Constructs a `context` object. References to the arguments are stored + /// in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + inline auto arg(string_view name) const -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(string_view name) const -> int { + return args_.get_id(name); + } + auto args() const -> const format_args& { return args_; } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() const -> iterator { return out_; } + + // Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; } +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } + +/// A compile-time format string. Use `format_string` in the public API to +/// prevent type deduction. +template struct fstring { + private: + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + string_view str; + using t = fstring; + + // Reports a compile-time error if S is not a valid format string for T. + template + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { + using namespace detail; + static_assert(count<(is_view>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); + if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { + auto sv = string_view(str); + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(sv, checker(sv, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { + FMT_CONSTEXPR auto sv = string_view(S()); + FMT_CONSTEXPR int unused = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(unused); + } + fstring(runtime_format_string<> fmt) : str(fmt.str) {} + + // Returning by reference generates better code in debug mode. + FMT_ALWAYS_INLINE operator const string_view&() const { return str; } + auto get() const -> string_view { return str; } +}; + +template using format_string = typename fstring::t; + +template +using is_formattable = bool_constant::value, int*, T>, Char>, + void>::value>; +#ifdef __cpp_concepts +template +concept formattable = is_formattable, Char>::value; +#endif + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor()> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + // Suppress warnings for pathological types convertible to detail::value. + FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") + return {{args...}}; +} + +template +using vargs = + detail::format_arg_store(), + detail::make_descriptor()>; + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function. + * + * **Example**: + * + * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + return {name, arg}; +} + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(out, fmt.str, vargs{{args...}}); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt.str, vargs{{args...}}); +} + +struct format_to_result { + /// Pointer to just after the last successful write in the array. + char* out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator char*() const { + // Report truncation to prevent silent data loss. + if (truncated) report_error("output is truncated"); + return out; + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(stdout, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(f, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + return detail::const_check(detail::use_utf8) + ? vprintln(f, fmt.str, va) + : detail::vprint_mojibake(f, fmt.str, va, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_PRAGMA_GCC(diagnostic pop) +FMT_PRAGMA_CLANG(diagnostic pop) +FMT_PRAGMA_GCC(pop_options) +FMT_END_EXPORT +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/chrono.h b/lib/spdlog/include/spdlog/fmt/bundled/chrono.h new file mode 100644 index 0000000..9fbeeed --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/chrono.h @@ -0,0 +1,2246 @@ +// Formatting library for C++ - chrono support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CHRONO_H_ +#define FMT_CHRONO_H_ + +#ifndef FMT_MODULE +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// Enable safe chrono durations, unless explicitly disabled. +#ifndef FMT_SAFE_DURATION_CAST +# define FMT_SAFE_DURATION_CAST 1 +#endif +#if FMT_SAFE_DURATION_CAST + +// For conversion between std::chrono::durations without undefined +// behaviour or erroneous results. +// This is a stripped down version of duration_cast, for inclusion in fmt. +// See https://github.com/pauldreik/safe_duration_cast +// +// Copyright Paul Dreik 2019 +namespace safe_duration_cast { + +// DEPRECATED! +template ::value && + std::numeric_limits::is_signed == + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + // A and B are both signed, or both unsigned. + if (detail::const_check(F::digits <= T::digits)) { + // From fits in To without any problem. + } else { + // From does not always fit in To, resort to a dynamic check. + if (from < (T::min)() || from > (T::max)()) { + // outside range. + ec = 1; + return {}; + } + } + return static_cast(from); +} + +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. +template ::value && + std::numeric_limits::is_signed != + std::numeric_limits::is_signed)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + using F = std::numeric_limits; + using T = std::numeric_limits; + static_assert(F::is_integer, "From must be integral"); + static_assert(T::is_integer, "To must be integral"); + + if (detail::const_check(F::is_signed && !T::is_signed)) { + // From may be negative, not allowed! + if (fmt::detail::is_negative(from)) { + ec = 1; + return {}; + } + // From is positive. Can it always fit in To? + if (detail::const_check(F::digits > T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + } + + if (detail::const_check(!F::is_signed && T::is_signed && + F::digits >= T::digits) && + from > static_cast(detail::max_value())) { + ec = 1; + return {}; + } + return static_cast(from); // Lossless conversion. +} + +template ::value)> +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { + ec = 0; + return from; +} // function + +// clang-format off +/** + * converts From to To if possible, otherwise ec is set. + * + * input | output + * ---------------------------------|--------------- + * NaN | NaN + * Inf | Inf + * normal, fits in output | converted (possibly lossy) + * normal, does not fit in output | ec is set + * subnormal | best effort + * -Inf | -Inf + */ +// clang-format on +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + using T = std::numeric_limits; + static_assert(std::is_floating_point::value, "From must be floating"); + static_assert(std::is_floating_point::value, "To must be floating"); + + // catch the only happy case + if (std::isfinite(from)) { + if (from >= T::lowest() && from <= (T::max)()) { + return static_cast(from); + } + // not within range. + ec = 1; + return {}; + } + + // nan and inf will be preserved + return static_cast(from); +} // function + +template ::value)> +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { + ec = 0; + static_assert(std::is_floating_point::value, "From must be floating"); + return from; +} + +/// Safe duration_cast between floating point durations +template ::value), + FMT_ENABLE_IF(std::is_floating_point::value)> +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { + using From = std::chrono::duration; + ec = 0; + + // the basic idea is that we need to convert from count() in the from type + // to count() in the To type, by multiplying it with this: + struct Factor + : std::ratio_divide {}; + + static_assert(Factor::num > 0, "num must be positive"); + static_assert(Factor::den > 0, "den must be positive"); + + // the conversion is like this: multiply from.count() with Factor::num + // /Factor::den and convert it to To::rep, all this without + // overflow/underflow. let's start by finding a suitable type that can hold + // both To, From and Factor::num + using IntermediateRep = + typename std::common_type::type; + + // force conversion of From::rep -> IntermediateRep to be safe, + // even if it will never happen be narrowing in this context. + IntermediateRep count = + safe_float_conversion(from.count(), ec); + if (ec) { + return {}; + } + + // multiply with Factor::num without overflow or underflow + if (detail::const_check(Factor::num != 1)) { + constexpr auto max1 = detail::max_value() / + static_cast(Factor::num); + if (count > max1) { + ec = 1; + return {}; + } + constexpr auto min1 = std::numeric_limits::lowest() / + static_cast(Factor::num); + if (count < min1) { + ec = 1; + return {}; + } + count *= static_cast(Factor::num); + } + + // this can't go wrong, right? den>0 is checked earlier. + if (detail::const_check(Factor::den != 1)) { + using common_t = typename std::common_type::type; + count /= static_cast(Factor::den); + } + + // convert to the to type, safely + using ToRep = typename To::rep; + + const ToRep tocount = safe_float_conversion(count, ec); + if (ec) { + return {}; + } + return To{tocount}; +} +} // namespace safe_duration_cast +#endif + +namespace detail { + +// Check if std::chrono::utc_time is available. +#ifdef FMT_USE_UTC_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_UTC_TIME 0 +#endif +#if FMT_USE_UTC_TIME +using utc_clock = std::chrono::utc_clock; +#else +struct utc_clock { + template void to_sys(T); +}; +#endif + +// Check if std::chrono::local_time is available. +#ifdef FMT_USE_LOCAL_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_LOCAL_TIME 0 +#endif +#if FMT_USE_LOCAL_TIME +using local_t = std::chrono::local_t; +#else +struct local_t {}; +#endif + +} // namespace detail + +template +using sys_time = std::chrono::time_point; + +template +using utc_time = std::chrono::time_point; + +template +using local_time = std::chrono::time_point; + +namespace detail { + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +template struct null {}; +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } + +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public StreamBuf { + private: + using char_type = typename StreamBuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename StreamBuf::int_type; + using traits_type = typename StreamBuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { + static const auto& locale = std::locale::classic(); + return locale; +} + +template struct codecvt_result { + static constexpr size_t max_size = 32; + CodeUnit buf[max_size]; + CodeUnit* end; +}; + +template +void write_codecvt(codecvt_result& out, string_view in, + const std::locale& loc) { + FMT_PRAGMA_CLANG(diagnostic push) + FMT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated") + auto& f = std::use_facet>(loc); + FMT_PRAGMA_CLANG(diagnostic pop) + auto mb = std::mbstate_t(); + const char* from_next = nullptr; + auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf), + std::end(out.buf), out.end); + if (result != std::codecvt_base::ok) + FMT_THROW(format_error("failed to format time")); +} + +template +auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) + -> OutputIt { + if (const_check(detail::use_utf8) && loc != get_classic_locale()) { + // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and + // gcc-4. +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) + // 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 + + using unit_t = codecvt_result; + unit_t unit; + write_codecvt(unit, in, loc); + // In UTF-8 is used one to four one-byte code units. + auto u = + to_utf8>(); + if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) + FMT_THROW(format_error("failed to format time")); + return copy(u.c_str(), u.c_str() + u.size(), out); + } + return copy(in.data(), in.data() + in.size(), out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + codecvt_result unit; + write_codecvt(unit, sv, loc); + return copy(unit.buf, unit.end, out); +} + +template ::value)> +auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) + -> OutputIt { + return write_encoded_tm_str(out, sv, loc); +} + +template +inline void do_write(buffer& buf, const std::tm& time, + const std::locale& loc, char format, char modifier) { + auto&& format_buf = formatbuf>(buf); + auto&& os = std::basic_ostream(&format_buf); + os.imbue(loc); + const auto& facet = std::use_facet>(loc); + auto end = facet.put(os, os, Char(' '), &time, format, modifier); + if (end.failed()) FMT_THROW(format_error("failed to format time")); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = get_buffer(out); + do_write(buf, time, loc, format, modifier); + return get_iterator(buf, out); +} + +template ::value)> +auto write(OutputIt out, const std::tm& time, const std::locale& loc, + char format, char modifier = 0) -> OutputIt { + auto&& buf = basic_memory_buffer(); + do_write(buf, time, loc, format, modifier); + return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); +} + +template +using is_similar_arithmetic_type = + bool_constant<(std::is_integral::value && std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)>; + +FMT_NORETURN inline void throw_duration_error() { + FMT_THROW(format_error("cannot format duration")); +} + +// Cast one integral duration to another with an overflow check. +template ::value&& + std::is_integral::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if !FMT_SAFE_DURATION_CAST + return std::chrono::duration_cast(from); +#else + // The conversion factor: to.count() == factor * from.count(). + using factor = std::ratio_divide; + + using common_rep = typename std::common_type::type; + common_rep count = from.count(); // This conversion is lossless. + + // Multiply from.count() by factor and check for overflow. + if (const_check(factor::num != 1)) { + if (count > max_value() / factor::num) throw_duration_error(); + const auto min = (std::numeric_limits::min)() / factor::num; + if (const_check(!std::is_unsigned::value) && count < min) + throw_duration_error(); + count *= factor::num; + } + if (const_check(factor::den != 1)) count /= factor::den; + int ec = 0; + auto to = + To(safe_duration_cast::lossless_integral_conversion( + count, ec)); + if (ec) throw_duration_error(); + return to; +#endif +} + +template ::value&& + std::is_floating_point::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Preserve infinity and NaN. + if (!isfinite(from.count())) return static_cast(from.count()); + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) throw_duration_error(); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template ::value)> +auto duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t(sys_time time_point) -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return detail::duration_cast>( + time_point.time_since_epoch()) + .count(); +} + +} // namespace detail + +FMT_BEGIN_EXPORT + +/** + * 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 auto gmtime(std::time_t time) -> std::tm { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + inline dispatcher(std::time_t t) : time_(t) {} + + inline auto run() -> bool { + using namespace fmt::detail; + return handle(gmtime_r(&time_, &tm_)); + } + + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } + + inline auto handle(detail::null<>) -> bool { + using namespace fmt::detail; + return fallback(gmtime_s(&tm_, &time_)); + } + + inline auto fallback(int res) -> bool { return res == 0; } + +#if !FMT_MSC_VERSION + inline auto fallback(detail::null<>) -> bool { + std::tm* tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != nullptr; + } +#endif + }; + auto gt = dispatcher(time); + // Too big time values may be unsupported. + if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); + return gt.tm_; +} + +template +inline auto gmtime(sys_time time_point) -> std::tm { + return gmtime(detail::to_time_t(time_point)); +} + +namespace detail { + +// Writes two-digit numbers a, b and c separated by sep to buf. +// The method by Pavel Novikov based on +// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. +inline void write_digit2_separated(char* buf, unsigned a, unsigned b, + unsigned c, char sep) { + unsigned long long digits = + a | (b << 24) | (static_cast(c) << 48); + // Convert each value to BCD. + // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. + // The difference is + // y - x = a * 6 + // a can be found from x: + // a = floor(x / 10) + // then + // y = x + a * 6 = x + floor(x / 10) * 6 + // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). + digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; + // Put low nibbles to high bytes and high nibbles to low bytes. + digits = ((digits & 0x00f00000f00000f0) >> 4) | + ((digits & 0x000f00000f00000f) << 8); + auto usep = static_cast(sep); + // Add ASCII '0' to each digit byte and insert separators. + digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); + + constexpr size_t len = 8; + if (const_check(is_big_endian())) { + char tmp[len]; + std::memcpy(tmp, &digits, len); + std::reverse_copy(tmp, tmp + len, buf); + } else { + std::memcpy(buf, &digits, len); + } +} + +template +FMT_CONSTEXPR inline auto get_units() -> const char* { + if (std::is_same::value) return "as"; + if (std::is_same::value) return "fs"; + if (std::is_same::value) return "ps"; + if (std::is_same::value) return "ns"; + if (std::is_same::value) + return detail::use_utf8 ? "µs" : "us"; + if (std::is_same::value) return "ms"; + if (std::is_same::value) return "cs"; + if (std::is_same::value) return "ds"; + if (std::is_same>::value) return "s"; + if (std::is_same::value) return "das"; + if (std::is_same::value) return "hs"; + if (std::is_same::value) return "ks"; + if (std::is_same::value) return "Ms"; + if (std::is_same::value) return "Gs"; + if (std::is_same::value) return "Ts"; + if (std::is_same::value) return "Ps"; + if (std::is_same::value) return "Es"; + if (std::is_same>::value) return "min"; + if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; + return nullptr; +} + +enum class numeric_system { + standard, + // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. + alternative +}; + +// Glibc extensions for formatting numeric values. +enum class pad_type { + // Pad a numeric result string with zeros (the default). + zero, + // Do not pad a numeric result string. + none, + // Pad a numeric result string with spaces. + space, +}; + +template +auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { + if (pad == pad_type::none) return out; + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); +} + +template +auto write_padding(OutputIt out, pad_type pad) -> OutputIt { + if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; + return out; +} + +// Parses a put_time-like format string and invokes handler actions. +template +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + if (begin == end || *begin == '}') return begin; + if (*begin != '%') FMT_THROW(format_error("invalid format")); + auto ptr = begin; + while (ptr != end) { + pad_type pad = pad_type::zero; + auto c = *ptr; + if (c == '}') break; + if (c != '%') { + ++ptr; + continue; + } + if (begin != ptr) handler.on_text(begin, ptr); + ++ptr; // consume '%' + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr; + switch (c) { + case '_': + pad = pad_type::space; + ++ptr; + break; + case '-': + pad = pad_type::none; + ++ptr; + break; + } + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case '%': handler.on_text(ptr - 1, ptr); break; + case 'n': { + const Char newline[] = {'\n'}; + handler.on_text(newline, newline + 1); + break; + } + case 't': { + const Char tab[] = {'\t'}; + handler.on_text(tab, tab + 1); + break; + } + // Year: + case 'Y': handler.on_year(numeric_system::standard, pad); break; + case 'y': handler.on_short_year(numeric_system::standard); break; + case 'C': handler.on_century(numeric_system::standard); break; + case 'G': handler.on_iso_week_based_year(); break; + case 'g': handler.on_iso_week_based_short_year(); break; + // Day of the week: + case 'a': handler.on_abbr_weekday(); break; + case 'A': handler.on_full_weekday(); break; + case 'w': handler.on_dec0_weekday(numeric_system::standard); break; + case 'u': handler.on_dec1_weekday(numeric_system::standard); break; + // Month: + case 'b': + case 'h': handler.on_abbr_month(); break; + case 'B': handler.on_full_month(); break; + case 'm': handler.on_dec_month(numeric_system::standard, pad); break; + // Day of the year/month: + case 'U': + handler.on_dec0_week_of_year(numeric_system::standard, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::standard, pad); + break; + case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break; + case 'j': handler.on_day_of_year(pad); break; + case 'd': handler.on_day_of_month(numeric_system::standard, pad); break; + case 'e': + handler.on_day_of_month(numeric_system::standard, pad_type::space); + break; + // Hour, minute, second: + case 'H': handler.on_24_hour(numeric_system::standard, pad); break; + case 'I': handler.on_12_hour(numeric_system::standard, pad); break; + case 'M': handler.on_minute(numeric_system::standard, pad); break; + case 'S': handler.on_second(numeric_system::standard, pad); break; + // Other: + case 'c': handler.on_datetime(numeric_system::standard); break; + case 'x': handler.on_loc_date(numeric_system::standard); break; + case 'X': handler.on_loc_time(numeric_system::standard); break; + case 'D': handler.on_us_date(); break; + case 'F': handler.on_iso_date(); break; + case 'r': handler.on_12_hour_time(); break; + case 'R': handler.on_24_hour_time(); break; + case 'T': handler.on_iso_time(); break; + case 'p': handler.on_am_pm(); break; + case 'Q': handler.on_duration_value(); break; + case 'q': handler.on_duration_unit(); break; + case 'z': handler.on_utc_offset(numeric_system::standard); break; + case 'Z': handler.on_tz_name(); break; + // Alternative representation: + case 'E': { + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'Y': handler.on_year(numeric_system::alternative, pad); break; + case 'y': handler.on_offset_year(); break; + case 'C': handler.on_century(numeric_system::alternative); break; + case 'c': handler.on_datetime(numeric_system::alternative); break; + case 'x': handler.on_loc_date(numeric_system::alternative); break; + case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + } + case 'O': + if (ptr == end) FMT_THROW(format_error("invalid format")); + c = *ptr++; + switch (c) { + case 'y': handler.on_short_year(numeric_system::alternative); break; + case 'm': handler.on_dec_month(numeric_system::alternative, pad); break; + case 'U': + handler.on_dec0_week_of_year(numeric_system::alternative, pad); + break; + case 'W': + handler.on_dec1_week_of_year(numeric_system::alternative, pad); + break; + case 'V': + handler.on_iso_week_of_year(numeric_system::alternative, pad); + break; + case 'd': + handler.on_day_of_month(numeric_system::alternative, pad); + break; + case 'e': + handler.on_day_of_month(numeric_system::alternative, pad_type::space); + break; + case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; + case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; + case 'H': handler.on_24_hour(numeric_system::alternative, pad); break; + case 'I': handler.on_12_hour(numeric_system::alternative, pad); break; + case 'M': handler.on_minute(numeric_system::alternative, pad); break; + case 'S': handler.on_second(numeric_system::alternative, pad); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); + } + break; + default: FMT_THROW(format_error("invalid format")); + } + begin = ptr; + } + if (begin != ptr) handler.on_text(begin, ptr); + return ptr; +} + +template struct null_chrono_spec_handler { + FMT_CONSTEXPR void unsupported() { + static_cast(this)->unsupported(); + } + FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_offset_year() { unsupported(); } + FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } + FMT_CONSTEXPR void on_iso_week_based_short_year() { 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_dec_month(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + 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(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_tz_name() { unsupported(); } +}; + +class tm_format_checker : public null_chrono_spec_handler { + private: + bool has_timezone_ = false; + + public: + constexpr explicit tm_format_checker(bool has_timezone) + : has_timezone_(has_timezone) {} + + FMT_NORETURN inline void unsupported() { + FMT_THROW(format_error("no format")); + } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_short_year(numeric_system) {} + FMT_CONSTEXPR void on_offset_year() {} + FMT_CONSTEXPR void on_century(numeric_system) {} + FMT_CONSTEXPR void on_iso_week_based_year() {} + FMT_CONSTEXPR void on_iso_week_based_short_year() {} + FMT_CONSTEXPR void on_abbr_weekday() {} + FMT_CONSTEXPR void on_full_weekday() {} + FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} + FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} + FMT_CONSTEXPR void on_abbr_month() {} + FMT_CONSTEXPR void on_full_month() {} + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_datetime(numeric_system) {} + FMT_CONSTEXPR void on_loc_date(numeric_system) {} + FMT_CONSTEXPR void on_loc_time(numeric_system) {} + FMT_CONSTEXPR void on_us_date() {} + FMT_CONSTEXPR void on_iso_date() {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) { + if (!has_timezone_) FMT_THROW(format_error("no timezone")); + } + FMT_CONSTEXPR void on_tz_name() { + if (!has_timezone_) FMT_THROW(format_error("no timezone")); + } +}; + +inline auto tm_wday_full_name(int wday) -> const char* { + static constexpr const char* full_name_list[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday"}; + return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; +} +inline auto tm_wday_short_name(int wday) -> const char* { + static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat"}; + return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; +} + +inline auto tm_mon_full_name(int mon) -> const char* { + static constexpr const char* full_name_list[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"}; + return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; +} +inline auto tm_mon_short_name(int mon) -> const char* { + static constexpr const char* short_name_list[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; +} + +template +struct has_tm_gmtoff : std::false_type {}; +template +struct has_tm_gmtoff> : std::true_type {}; + +template struct has_tm_zone : std::false_type {}; +template +struct has_tm_zone> : std::true_type {}; + +template ::value)> +auto set_tm_zone(T& time, char* tz) -> bool { + time.tm_zone = tz; + return true; +} +template ::value)> +auto set_tm_zone(T&, char*) -> bool { + return false; +} + +inline auto utc() -> char* { + static char tz[] = "UTC"; + return tz; +} + +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(format_error("chrono value is out of range")); + } + return static_cast(value); +} +template ::value)> +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return int_value; +} + +constexpr auto pow10(std::uint32_t n) -> long long { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +// Format subseconds which are given as an integer type with an appropriate +// number of digits. +template +void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, pow10(num_fractional_digits)>>; + + const auto fractional = d - detail::duration_cast(d); + const auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : detail::duration_cast(fractional).count(); + auto n = static_cast>(subseconds); + const int num_digits = count_digits(n); + + int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); + if (precision < 0) { + FMT_ASSERT(!std::is_floating_point::value, ""); + if (std::ratio_less::value) { + *out++ = '.'; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits); + } + } else if (precision > 0) { + *out++ = '.'; + leading_zeroes = min_of(leading_zeroes, precision); + int remaining = precision - leading_zeroes; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(pow10(to_unsigned(num_truncated_digits))); + if (n != 0) out = format_decimal(out, n, remaining); + return; + } + if (n != 0) { + out = format_decimal(out, n, num_digits); + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); + } +} + +// Format subseconds which are given as a floating point type with an +// appropriate number of digits. We cannot pass the Duration here, as we +// explicitly need to pass the Rep value in the duration_formatter. +template +void write_floating_seconds(memory_buffer& buf, Duration duration, + int num_fractional_digits = -1) { + using rep = typename Duration::rep; + FMT_ASSERT(std::is_floating_point::value, ""); + + auto val = duration.count(); + + if (num_fractional_digits < 0) { + // For `std::round` with fallback to `round`: + // On some toolchains `std::round` is not available (e.g. GCC 6). + using namespace std; + num_fractional_digits = + count_fractional_digits::value; + if (num_fractional_digits < 6 && static_cast(round(val)) != val) + num_fractional_digits = 6; + } + + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); +} + +template +class tm_writer { + private: + static constexpr int days_per_week = 7; + + const std::locale& loc_; + bool is_classic_; + OutputIt out_; + const Duration* subsecs_; + const std::tm& tm_; + + auto tm_sec() const noexcept -> int { + FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); + return tm_.tm_sec; + } + auto tm_min() const noexcept -> int { + FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); + return tm_.tm_min; + } + auto tm_hour() const noexcept -> int { + FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); + return tm_.tm_hour; + } + auto tm_mday() const noexcept -> int { + FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); + return tm_.tm_mday; + } + auto tm_mon() const noexcept -> int { + FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); + return tm_.tm_mon; + } + auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } + auto tm_wday() const noexcept -> int { + FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); + return tm_.tm_wday; + } + auto tm_yday() const noexcept -> int { + FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); + return tm_.tm_yday; + } + + auto tm_hour12() const noexcept -> int { + auto h = tm_hour(); + auto z = h < 12 ? h : h - 12; + return z == 0 ? 12 : z; + } + + // POSIX and the C Standard are unclear or inconsistent about what %C and %y + // do if the year is negative or exceeds 9999. Use the convention that %C + // concatenated with %y yields the same output as %Y, and that %Y contains at + // least 4 characters, with more only if necessary. + auto split_year_lower(long long year) const noexcept -> int { + auto l = year % 100; + if (l < 0) l = -l; // l in [0, 99] + return static_cast(l); + } + + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. + auto iso_year_weeks(long long curr_year) const noexcept -> int { + auto prev_year = curr_year - 1; + auto curr_p = + (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % + days_per_week; + auto prev_p = + (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % + days_per_week; + return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); + } + auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { + return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / + days_per_week; + } + auto tm_iso_week_year() const noexcept -> long long { + auto year = tm_year(); + auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return year - 1; + if (w > iso_year_weeks(year)) return year + 1; + return year; + } + auto tm_iso_week_of_year() const noexcept -> int { + auto year = tm_year(); + auto w = iso_week_num(tm_yday(), tm_wday()); + if (w < 1) return iso_year_weeks(year - 1); + if (w > iso_year_weeks(year)) return 1; + return w; + } + + void write1(int value) { + *out_++ = static_cast('0' + to_unsigned(value) % 10); + } + void write2(int value) { + const char* d = digits2(to_unsigned(value) % 100); + *out_++ = *d++; + *out_++ = *d; + } + void write2(int value, pad_type pad) { + unsigned int v = to_unsigned(value) % 100; + if (v >= 10) { + const char* d = digits2(v); + *out_++ = *d++; + *out_++ = *d; + } else { + out_ = detail::write_padding(out_, pad); + *out_++ = static_cast('0' + v); + } + } + + void write_year_extended(long long year, pad_type pad) { + // At least 4 characters. + int width = 4; + bool negative = year < 0; + if (negative) { + year = 0 - year; + --width; + } + uint32_or_64_or_128_t n = to_unsigned(year); + const int num_digits = count_digits(n); + if (negative && pad == pad_type::zero) *out_++ = '-'; + if (width > num_digits) + out_ = detail::write_padding(out_, pad, width - num_digits); + if (negative && pad != pad_type::zero) *out_++ = '-'; + out_ = format_decimal(out_, n, num_digits); + } + void write_year(long long year, pad_type pad) { + write_year_extended(year, pad); + } + + void write_utc_offset(long long offset, numeric_system ns) { + if (offset < 0) { + *out_++ = '-'; + offset = -offset; + } else { + *out_++ = '+'; + } + offset /= 60; + write2(static_cast(offset / 60)); + if (ns != numeric_system::standard) *out_++ = ':'; + write2(static_cast(offset % 60)); + } + + template ::value)> + void format_utc_offset(const T& tm, numeric_system ns) { + write_utc_offset(tm.tm_gmtoff, ns); + } + template ::value)> + void format_utc_offset(const T&, numeric_system ns) { + write_utc_offset(0, ns); + } + + template ::value)> + void format_tz_name(const T& tm) { + out_ = write_tm_str(out_, tm.tm_zone, loc_); + } + template ::value)> + void format_tz_name(const T&) { + out_ = std::copy_n(utc(), 3, out_); + } + + void format_localized(char format, char modifier = 0) { + out_ = write(out_, tm_, loc_, format, modifier); + } + + public: + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, + const Duration* subsecs = nullptr) + : loc_(loc), + is_classic_(loc_ == get_classic_locale()), + out_(out), + subsecs_(subsecs), + tm_(tm) {} + + auto out() const -> OutputIt { return out_; } + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + out_ = copy(begin, end, out_); + } + + void on_abbr_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_short_name(tm_wday())); + else + format_localized('a'); + } + void on_full_weekday() { + if (is_classic_) + out_ = write(out_, tm_wday_full_name(tm_wday())); + else + format_localized('A'); + } + void on_dec0_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); + format_localized('w', 'O'); + } + void on_dec1_weekday(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write1(wday == 0 ? days_per_week : wday); + } else { + format_localized('u', 'O'); + } + } + + void on_abbr_month() { + if (is_classic_) + out_ = write(out_, tm_mon_short_name(tm_mon())); + else + format_localized('b'); + } + void on_full_month() { + if (is_classic_) + out_ = write(out_, tm_mon_full_name(tm_mon())); + else + format_localized('B'); + } + + void on_datetime(numeric_system ns) { + if (is_classic_) { + on_abbr_weekday(); + *out_++ = ' '; + on_abbr_month(); + *out_++ = ' '; + on_day_of_month(numeric_system::standard, pad_type::space); + *out_++ = ' '; + on_iso_time(); + *out_++ = ' '; + on_year(numeric_system::standard, pad_type::space); + } else { + format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); + } + } + void on_loc_date(numeric_system ns) { + if (is_classic_) + on_us_date(); + else + format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_loc_time(numeric_system ns) { + if (is_classic_) + on_iso_time(); + else + format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); + } + void on_us_date() { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_mon() + 1), + to_unsigned(tm_mday()), + to_unsigned(split_year_lower(tm_year())), '/'); + out_ = copy(std::begin(buf), std::end(buf), out_); + } + void on_iso_date() { + auto year = tm_year(); + char buf[10]; + size_t offset = 0; + if (year >= 0 && year < 10000) { + write2digits(buf, static_cast(year / 100)); + } else { + offset = 4; + write_year_extended(year, pad_type::zero); + year = 0; + } + write_digit2_separated(buf + 2, static_cast(year % 100), + to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), + '-'); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); + } + + void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); } + void on_tz_name() { format_tz_name(tm_); } + + void on_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write_year(tm_year(), pad); + format_localized('Y', 'E'); + } + void on_short_year(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) + return write2(split_year_lower(tm_year())); + format_localized('y', 'O'); + } + void on_offset_year() { + if (is_classic_) return write2(split_year_lower(tm_year())); + format_localized('y', 'E'); + } + + void on_century(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) { + auto year = tm_year(); + auto upper = year / 100; + if (year >= -99 && year < 0) { + // Zero upper on negative year. + *out_++ = '-'; + *out_++ = '0'; + } else if (upper >= 0 && upper < 100) { + write2(static_cast(upper)); + } else { + out_ = write(out_, upper); + } + } else { + format_localized('C', 'E'); + } + } + + void on_dec_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mon() + 1, pad); + format_localized('m', 'O'); + } + + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); + format_localized('U', 'O'); + } + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + auto wday = tm_wday(); + write2((tm_yday() + days_per_week - + (wday == 0 ? (days_per_week - 1) : (wday - 1))) / + days_per_week, + pad); + } else { + format_localized('W', 'O'); + } + } + void on_iso_week_of_year(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_iso_week_of_year(), pad); + format_localized('V', 'O'); + } + + void on_iso_week_based_year() { + write_year(tm_iso_week_year(), pad_type::zero); + } + void on_iso_week_based_short_year() { + write2(split_year_lower(tm_iso_week_year())); + } + + void on_day_of_year(pad_type pad) { + auto yday = tm_yday() + 1; + auto digit1 = yday / 100; + if (digit1 != 0) + write1(digit1); + else + out_ = detail::write_padding(out_, pad); + write2(yday % 100, pad); + } + + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour(), pad); + format_localized('H', 'O'); + } + void on_12_hour(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_hour12(), pad); + format_localized('I', 'O'); + } + void on_minute(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_min(), pad); + format_localized('M', 'O'); + } + + void on_second(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) { + write2(tm_sec(), pad); + if (subsecs_) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, *subsecs_); + if (buf.size() > 1) { + // Remove the leading "0", write something like ".123". + out_ = copy(buf.begin() + 1, buf.end(), out_); + } + } else { + write_fractional_seconds(out_, *subsecs_); + } + } + } else { + // Currently no formatting of subseconds when a locale is set. + format_localized('S', 'O'); + } + } + + void on_12_hour_time() { + if (is_classic_) { + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour12()), + to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); + out_ = copy(std::begin(buf), std::end(buf), out_); + *out_++ = ' '; + on_am_pm(); + } else { + format_localized('r'); + } + } + void on_24_hour_time() { + write2(tm_hour()); + *out_++ = ':'; + write2(tm_min()); + } + void on_iso_time() { + on_24_hour_time(); + *out_++ = ':'; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (is_classic_) { + *out_++ = tm_hour() < 12 ? 'A' : 'P'; + *out_++ = 'M'; + } else { + format_localized('p'); + } + } + + // These apply to chrono durations but not tm. + void on_duration_value() {} + void on_duration_unit() {} +}; + +struct chrono_format_checker : null_chrono_spec_handler { + bool has_precision_integral = false; + + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } + + template + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_12_hour_time() {} + FMT_CONSTEXPR void on_24_hour_time() {} + FMT_CONSTEXPR void on_iso_time() {} + FMT_CONSTEXPR void on_am_pm() {} + FMT_CONSTEXPR void on_duration_value() const { + if (has_precision_integral) + FMT_THROW(format_error("precision not allowed for this argument type")); + } + FMT_CONSTEXPR void on_duration_unit() {} +}; + +template ::value&& has_isfinite::value)> +inline auto isfinite(T) -> bool { + return true; +} + +template ::value)> +inline auto mod(T x, int y) -> T { + return x % static_cast(y); +} +template ::value)> +inline auto mod(T x, int y) -> T { + return std::fmod(x, static_cast(y)); +} + +// If T is an integral type, maps T to its unsigned counterpart, otherwise +// leaves it unchanged (unlike std::make_unsigned). +template ::value> +struct make_unsigned_or_unchanged { + using type = T; +}; + +template struct make_unsigned_or_unchanged { + using type = typename std::make_unsigned::type; +}; + +template ::value)> +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { + // This may overflow and/or the result may not fit in the target type. +#if FMT_SAFE_DURATION_CAST + using common_seconds_type = + typename std::common_type::type; + auto d_as_common = detail::duration_cast(d); + auto d_as_whole_seconds = + detail::duration_cast(d_as_common); + // This conversion should be nonproblematic. + auto diff = d_as_common - d_as_whole_seconds; + auto ms = detail::duration_cast>(diff); + return ms; +#else + auto s = detail::duration_cast(d); + return detail::duration_cast(d - s); +#endif +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { + return write(out, val); +} + +template ::value)> +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); + specs.precision = precision; + specs.set_type(precision >= 0 ? presentation_type::fixed + : presentation_type::general); + return write(out, val, specs); +} + +template +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { + return copy(unit.begin(), unit.end(), out); +} + +template +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { + // 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 copy(u.c_str(), u.c_str() + u.size(), out); +} + +template +auto format_duration_unit(OutputIt out) -> OutputIt { + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); + *out++ = '['; + out = write(out, Period::num); + if (const_check(Period::den != 1)) { + *out++ = '/'; + out = write(out, Period::den); + } + *out++ = ']'; + *out++ = 's'; + return out; +} + +class get_locale { + private: + union { + std::locale locale_; + }; + bool has_locale_ = false; + + public: + inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + if (!localized) return; + ignore_unused(loc); + ::new (&locale_) std::locale( +#if FMT_USE_LOCALE + loc.template get() +#endif + ); + } + inline ~get_locale() { + if (has_locale_) locale_.~locale(); + } + inline operator const std::locale&() const { + return has_locale_ ? locale_ : get_classic_locale(); + } +}; + +template +struct duration_formatter { + using iterator = basic_appender; + iterator out; + // rep is unsigned to avoid overflow. + using rep = + conditional_t::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged::type>; + rep val; + int precision; + locale_ref locale; + bool localized = false; + using seconds = std::chrono::duration; + seconds s; + using milliseconds = std::chrono::duration; + bool negative; + + using tm_writer_type = tm_writer; + + duration_formatter(iterator o, std::chrono::duration d, + locale_ref loc) + : out(o), val(static_cast(d.count())), locale(loc), negative(false) { + if (d.count() < 0) { + val = 0 - val; + negative = true; + } + + // this may overflow and/or the result may not fit in the + // target type. + // might need checked conversion (rep!=Rep) + s = detail::duration_cast(std::chrono::duration(val)); + } + + // returns true if nan or inf, writes to out. + auto handle_nan_inf() -> bool { + if (isfinite(val)) return false; + if (isnan(val)) { + write_nan(); + return true; + } + // must be +-inf + if (val > 0) + std::copy_n("inf", 3, out); + else + std::copy_n("-inf", 4, out); + return true; + } + + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } + + auto hour12() const -> Rep { + Rep hour = static_cast(mod((s.count() / 3600), 12)); + return hour <= 0 ? 12 : hour; + } + + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } + + auto time() const -> std::tm { + auto time = std::tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + time.tm_min = to_nonnegative_int(minute(), 60); + time.tm_sec = to_nonnegative_int(second(), 60); + return time; + } + + void write_sign() { + if (!negative) return; + *out++ = '-'; + negative = false; + } + + void write(Rep value, int width, pad_type pad = pad_type::zero) { + write_sign(); + if (isnan(value)) return write_nan(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(value, max_value())); + int num_digits = detail::count_digits(n); + if (width > num_digits) { + out = detail::write_padding(out, pad, width - num_digits); + } + out = format_decimal(out, n, num_digits); + } + + void write_nan() { std::copy_n("nan", 3, out); } + + template + void format_tm(const tm& time, Callback cb, Args... args) { + if (isnan(val)) return write_nan(); + get_locale loc(localized, locale); + auto w = tm_writer_type(loc, out, time); + (w.*cb)(args...); + out = w.out(); + } + + void on_text(const Char* begin, const Char* end) { + copy(begin, end, out); + } + + // These are not implemented because durations don't have date information. + void on_abbr_weekday() {} + void on_full_weekday() {} + void on_dec0_weekday(numeric_system) {} + void on_dec1_weekday(numeric_system) {} + void on_abbr_month() {} + void on_full_month() {} + void on_datetime(numeric_system) {} + void on_loc_date(numeric_system) {} + void on_loc_time(numeric_system) {} + void on_us_date() {} + void on_iso_date() {} + void on_utc_offset(numeric_system) {} + void on_tz_name() {} + void on_year(numeric_system, pad_type) {} + void on_short_year(numeric_system) {} + void on_offset_year() {} + void on_century(numeric_system) {} + void on_iso_week_based_year() {} + void on_iso_week_based_short_year() {} + void on_dec_month(numeric_system, pad_type) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year(pad_type) { + if (handle_nan_inf()) return; + write(days(), 0); + } + + void on_24_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour(), 24); + format_tm(time, &tm_writer_type::on_24_hour, ns, pad); + } + + void on_12_hour(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(hour12(), 2, pad); + auto time = tm(); + time.tm_hour = to_nonnegative_int(hour12(), 12); + format_tm(time, &tm_writer_type::on_12_hour, ns, pad); + } + + void on_minute(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) return write(minute(), 2, pad); + auto time = tm(); + time.tm_min = to_nonnegative_int(minute(), 60); + format_tm(time, &tm_writer_type::on_minute, ns, pad); + } + + void on_second(numeric_system ns, pad_type pad) { + if (handle_nan_inf()) return; + + if (ns == numeric_system::standard) { + if (std::is_floating_point::value) { + auto buf = memory_buffer(); + write_floating_seconds(buf, std::chrono::duration(val), + precision); + if (negative) *out++ = '-'; + if (buf.size() < 2 || buf[1] == '.') + out = detail::write_padding(out, pad); + out = copy(buf.begin(), buf.end(), out); + } else { + write(second(), 2, pad); + write_fractional_seconds( + out, std::chrono::duration(val), precision); + } + return; + } + auto time = tm(); + time.tm_sec = to_nonnegative_int(second(), 60); + format_tm(time, &tm_writer_type::on_second, ns, pad); + } + + void on_12_hour_time() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_12_hour_time); + } + + void on_24_hour_time() { + if (handle_nan_inf()) { + *out++ = ':'; + handle_nan_inf(); + return; + } + + write(hour(), 2); + *out++ = ':'; + write(minute(), 2); + } + + void on_iso_time() { + on_24_hour_time(); + *out++ = ':'; + if (handle_nan_inf()) return; + on_second(numeric_system::standard, pad_type::zero); + } + + void on_am_pm() { + if (handle_nan_inf()) return; + format_tm(time(), &tm_writer_type::on_am_pm); + } + + void on_duration_value() { + if (handle_nan_inf()) return; + write_sign(); + out = format_duration_value(out, val, precision); + } + + void on_duration_unit() { out = format_duration_unit(out); } +}; + +} // namespace detail + +#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 +using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; +#else +// A fallback version of weekday. +class weekday { + private: + unsigned char value_; + + public: + weekday() = default; + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } +}; + +class day { + private: + unsigned char value_; + + public: + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class month { + private: + unsigned char value_; + + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } +}; + +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; +#endif // __cpp_lib_chrono >= 201907 + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + this->set_localized(); + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_wday = static_cast(wd.c_encoding()); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(this->localized(), ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_weekday(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + this->set_localized(); + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(this->localized(), ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + +template +struct formatter, Char> { + private: + format_specs specs_; + detail::arg_ref width_ref_; + detail::arg_ref precision_ref_; + basic_string_view fmt_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + auto checker = detail::chrono_format_checker(); + if (*it == '.') { + checker.has_precision_integral = !std::is_floating_point::value; + it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); + } + if (it != end && *it == 'L') { + specs_.set_localized(); + ++it; + } + end = detail::parse_chrono_format(it, end, checker); + fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto format(std::chrono::duration d, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + auto precision = specs.precision; + specs.precision = -1; + auto begin = fmt_.begin(), end = fmt_.end(); + // As a possible future optimization, we could avoid extra copying if width + // is not specified. + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), precision, + precision_ref_, ctx); + if (begin == end || *begin == '}') { + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); + } else { + auto f = + detail::duration_formatter(out, d, ctx.locale()); + f.precision = precision; + f.localized = specs_.localized(); + detail::parse_chrono_format(begin, end, f); + } + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + basic_string_view fmt_ = + detail::string_literal(); + + protected: + auto localized() const -> bool { return specs_.localized(); } + FMT_CONSTEXPR void set_localized() { specs_.set_localized(); } + + FMT_CONSTEXPR auto do_parse(parse_context& ctx, bool has_timezone) + -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end || *it == '}') return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } + + if (*it == 'L') { + specs_.set_localized(); + ++it; + } + + end = detail::parse_chrono_format(it, end, + detail::tm_format_checker(has_timezone)); + // Replace the default format string only if the new spec is not empty. + if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; + return end; + } + + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + + auto loc_ref = specs.localized() ? ctx.locale() : locale_ref(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = detail::tm_writer, Char, Duration>( + loc, out, tm, subsecs); + detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx, detail::has_tm_gmtoff::value); + } + + template + auto format(const std::tm& tm, FormatContext& ctx) const + -> decltype(ctx.out()) { + return do_format(tm, ctx, nullptr); + } +}; + +// DEPRECATED! Reversed order of template parameters. +template +struct formatter, Char> : private formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, true); + } + + template + auto format(sys_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + detail::set_tm_zone(tm, detail::utc()); + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = detail::duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) { + --tm.tm_sec; + } else { + tm = gmtime(val - second); + detail::set_tm_zone(tm, detail::utc()); + } + subsecs += second; + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +template +struct formatter, Char> + : formatter, Char> { + template + auto format(utc_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format( + detail::utc_clock::to_sys(val), ctx); + } +}; + +template +struct formatter, Char> + : private formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, false); + } + + template + auto format(local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time_since_epoch = val.time_since_epoch(); + auto seconds_since_epoch = + detail::duration_cast(time_since_epoch); + // Use gmtime to prevent time zone conversion since local_time has an + // unspecified time zone. + std::tm t = gmtime(seconds_since_epoch.count()); + using period = typename Duration::period; + if (period::num == 1 && period::den == 1 && + !std::is_floating_point::value) { + return formatter::format(t, ctx); + } + auto subsecs = + detail::duration_cast(time_since_epoch - seconds_since_epoch); + return formatter::do_format(t, ctx, &subsecs); + } +}; + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_CHRONO_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/color.h b/lib/spdlog/include/spdlog/fmt/bundled/color.h new file mode 100644 index 0000000..2cbc53c --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/color.h @@ -0,0 +1,637 @@ +// Formatting library for C++ - color support +// +// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COLOR_H_ +#define FMT_COLOR_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +enum class color : uint32_t { + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aquamarine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32 // rgb(154,205,50) +}; // enum class color + +enum class terminal_color : uint8_t { + black = 30, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black = 90, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white +}; + +enum class emphasis : uint8_t { + bold = 1, + faint = 1 << 1, + italic = 1 << 2, + underline = 1 << 3, + blink = 1 << 4, + reverse = 1 << 5, + conceal = 1 << 6, + strikethrough = 1 << 7, +}; + +// rgb is a struct for red, green and blue colors. +// Using the name "rgb" makes some editors show the color in a tooltip. +struct rgb { + constexpr rgb() : r(0), g(0), b(0) {} + constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + constexpr rgb(uint32_t hex) + : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} + constexpr rgb(color hex) + : r((uint32_t(hex) >> 16) & 0xFF), + g((uint32_t(hex) >> 8) & 0xFF), + b(uint32_t(hex) & 0xFF) {} + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace detail { + +// A bit-packed variant of an RGB color, a terminal color, or unset color. +// see text_style for the bit-packing scheme. +struct color_type { + constexpr color_type() noexcept = default; + constexpr color_type(color rgb_color) noexcept + : value_(static_cast(rgb_color) | (1 << 24)) {} + constexpr color_type(rgb rgb_color) noexcept + : color_type(static_cast( + (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b)) {} + constexpr color_type(terminal_color term_color) noexcept + : value_(static_cast(term_color) | (3 << 24)) {} + + constexpr auto is_terminal_color() const noexcept -> bool { + return (value_ & (1 << 25)) != 0; + } + + constexpr auto value() const noexcept -> uint32_t { + return value_ & 0xFFFFFF; + } + + constexpr color_type(uint32_t value) noexcept : value_(value) {} + + uint32_t value_ = 0; +}; +} // namespace detail + +/// A text style consisting of foreground and background colors and emphasis. +class text_style { + // The information is packed as follows: + // ┌──┐ + // │ 0│─┐ + // │..│ ├── foreground color value + // │23│─┘ + // ├──┤ + // │24│─┬── discriminator for the above value. 00 if unset, 01 if it's + // │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused) + // ├──┤ + // │26│──── overflow bit, always zero (see below) + // ├──┤ + // │27│─┐ + // │..│ │ + // │50│ │ + // ├──┤ │ + // │51│ ├── background color (same format as the foreground color) + // │52│ │ + // ├──┤ │ + // │53│─┘ + // ├──┤ + // │54│─┐ + // │..│ ├── emphases + // │61│─┘ + // ├──┤ + // │62│─┬── unused + // │63│─┘ + // └──┘ + // The overflow bits are there to make operator|= efficient. + // When ORing, we must throw if, for either the foreground or background, + // one style specifies a terminal color and the other specifies any color + // (terminal or RGB); in other words, if one discriminator is 11 and the + // other is 11 or 01. + // + // We do that check by adding the styles. Consider what adding does to each + // possible pair of discriminators: + // 00 + 00 = 000 + // 01 + 00 = 001 + // 11 + 00 = 011 + // 01 + 01 = 010 + // 11 + 01 = 100 (!!) + // 11 + 11 = 110 (!!) + // In the last two cases, the ones we want to catch, the third bit——the + // overflow bit——is set. Bingo. + // + // We must take into account the possible carry bit from the bits + // before the discriminator. The only potentially problematic case is + // 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry + // bit is impossible in that case, because 00 (unset color) means the + // 24 bits that precede the discriminator are all zero. + // + // This test can be applied to both colors simultaneously. + + public: + FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept + : style_(static_cast(em) << 54) {} + + FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& { + if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0) + report_error("can't OR a terminal color"); + style_ |= rhs.style_; + return *this; + } + + friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs) + -> text_style { + return lhs |= rhs; + } + + FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool { + return style_ == rhs.style_; + } + + FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool { + return !(*this == rhs); + } + + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { + return (style_ & (1 << 24)) != 0; + } + FMT_CONSTEXPR auto has_background() const noexcept -> bool { + return (style_ & (1ULL << 51)) != 0; + } + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { + return (style_ >> 54) != 0; + } + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { + FMT_ASSERT(has_foreground(), "no foreground specified for this style"); + return style_ & 0x3FFFFFF; + } + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { + FMT_ASSERT(has_background(), "no background specified for this style"); + return (style_ >> 27) & 0x3FFFFFF; + } + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { + FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); + return static_cast(style_ >> 54); + } + + private: + FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {} + + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; + + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; + + uint64_t style_ = 0; +}; + +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { + return foreground.value_; +} + +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { + return static_cast(background.value_) << 27; +} + +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { + return text_style(lhs) | rhs; +} + +namespace detail { + +template struct ansi_color_escape { + FMT_CONSTEXPR ansi_color_escape(color_type text_color, + const char* esc) noexcept { + // If we have a terminal color, we need to output another escape code + // sequence. + if (text_color.is_terminal_color()) { + bool is_background = esc == string_view("\x1b[48;2;"); + uint32_t value = text_color.value(); + // Background ASCII codes are the same as the foreground ones but with + // 10 more. + if (is_background) value += 10u; + + buffer[size++] = static_cast('\x1b'); + buffer[size++] = static_cast('['); + + if (value >= 100u) { + buffer[size++] = static_cast('1'); + value %= 100u; + } + buffer[size++] = static_cast('0' + value / 10u); + buffer[size++] = static_cast('0' + value % 10u); + + buffer[size++] = static_cast('m'); + return; + } + + for (int i = 0; i < 7; i++) { + buffer[i] = static_cast(esc[i]); + } + rgb color(text_color.value()); + to_esc(color.r, buffer + 7, ';'); + to_esc(color.g, buffer + 11, ';'); + to_esc(color.b, buffer + 15, 'm'); + size = 19; + } + FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { + uint8_t em_codes[num_emphases] = {}; + if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; + if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; + if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; + if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; + if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; + if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; + if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; + if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; + + buffer[size++] = static_cast('\x1b'); + buffer[size++] = static_cast('['); + + for (size_t i = 0; i < num_emphases; ++i) { + if (!em_codes[i]) continue; + buffer[size++] = static_cast('0' + em_codes[i]); + buffer[size++] = static_cast(';'); + } + + buffer[size - 1] = static_cast('m'); + } + FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } + + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR auto end() const noexcept -> const Char* { + return buffer + size; + } + + private: + static constexpr size_t num_emphases = 8; + Char buffer[7u + 4u * num_emphases] = {}; + size_t size = 0; + + static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, + char delimiter) noexcept { + out[0] = static_cast('0' + c / 100); + out[1] = static_cast('0' + c / 10 % 10); + out[2] = static_cast('0' + c % 10); + out[3] = static_cast(delimiter); + } + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { + return static_cast(em) & static_cast(mask); + } +}; + +template +FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept + -> ansi_color_escape { + return ansi_color_escape(foreground, "\x1b[38;2;"); +} + +template +FMT_CONSTEXPR auto make_background_color(color_type background) noexcept + -> ansi_color_escape { + return ansi_color_escape(background, "\x1b[48;2;"); +} + +template +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { + return ansi_color_escape(em); +} + +template inline void reset_color(buffer& buffer) { + auto reset_color = string_view("\x1b[0m"); + buffer.append(reset_color.begin(), reset_color.end()); +} + +template struct styled_arg : view { + const T& value; + text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} +}; + +template +void vformat_to(buffer& buf, text_style ts, basic_string_view fmt, + basic_format_args> args) { + if (ts.has_emphasis()) { + auto emphasis = make_emphasis(ts.get_emphasis()); + buf.append(emphasis.begin(), emphasis.end()); + } + if (ts.has_foreground()) { + auto foreground = make_foreground_color(ts.get_foreground()); + buf.append(foreground.begin(), foreground.end()); + } + if (ts.has_background()) { + auto background = make_background_color(ts.get_background()); + buf.append(background.begin(), background.end()); + } + vformat_to(buf, fmt, args); + if (ts != text_style()) reset_color(buf); +} +} // namespace detail + +inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); +} + +/** + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(FILE* f, text_style ts, format_string fmt, T&&... args) { + vprint(f, ts, fmt.str, vargs{{args...}}); +} + +/** + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); + */ +template +void print(text_style ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); +} + +inline auto vformat(text_style ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return fmt::to_string(buf); +} + +/** + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(text_style ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt.str, vargs{{args...}}); +} + +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); + return detail::get_iterator(buf, out); +} + +/** + * 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 out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, text_style ts, format_string fmt, + T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt.str, vargs{{args...}}); +} + +template +struct formatter, Char> : formatter { + template + auto format(const detail::styled_arg& arg, FormatContext& ctx) const + -> decltype(ctx.out()) { + const auto& ts = arg.style; + auto out = ctx.out(); + + bool has_style = false; + if (ts.has_emphasis()) { + has_style = true; + auto emphasis = detail::make_emphasis(ts.get_emphasis()); + out = detail::copy(emphasis.begin(), emphasis.end(), out); + } + if (ts.has_foreground()) { + has_style = true; + auto foreground = + detail::make_foreground_color(ts.get_foreground()); + out = detail::copy(foreground.begin(), foreground.end(), out); + } + if (ts.has_background()) { + has_style = true; + auto background = + detail::make_background_color(ts.get_background()); + out = detail::copy(background.begin(), background.end(), out); + } + out = formatter::format(arg.value, ctx); + if (has_style) { + auto reset_color = string_view("\x1b[0m"); + out = detail::copy(reset_color.begin(), reset_color.end(), out); + } + return out; + } +}; + +/** + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); + */ +template +FMT_CONSTEXPR auto styled(const T& value, text_style ts) + -> detail::styled_arg> { + return detail::styled_arg>{value, ts}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COLOR_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/compile.h b/lib/spdlog/include/spdlog/fmt/bundled/compile.h new file mode 100644 index 0000000..64eb7a2 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/compile.h @@ -0,0 +1,588 @@ +// Formatting library for C++ - experimental format string compilation +// +// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_COMPILE_H_ +#define FMT_COMPILE_H_ + +#ifndef FMT_MODULE +# include // std::back_inserter +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +// A compile-time string which is compiled into fast formatting code. +class compiled_string {}; + +template +struct is_compiled_string : std::is_base_of {}; + +/** + * 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. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); + */ +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) +#else +# define FMT_COMPILE(s) FMT_STRING(s) +#endif + +/** + * Converts a string literal into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires support + * for class types in constant template parameters (a C++20 feature). + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * using namespace fmt::literals; + * std::string s = fmt::format("{}"_cf, 42); + */ +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +template constexpr auto operator""_cf() { + return FMT_COMPILE(Str.data); +} +} // namespace literals +#endif + +FMT_END_EXPORT + +namespace detail { + +template +constexpr auto first(const T& value, const Tail&...) -> const T& { + return value; +} + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +template struct type_list {}; + +// Returns a reference to the argument at index N from [first, rest...]. +template +constexpr auto get([[maybe_unused]] const T& first, + [[maybe_unused]] const Args&... rest) -> const auto& { + static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); + if constexpr (N == 0) + return first; + else + return detail::get(rest...); +} + +# if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_static_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +# endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +# if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +# endif + (void)name; + return -1; +} + +template +constexpr auto get_arg_index_by_name(basic_string_view name, + type_list) -> int { + return get_arg_index_by_name(name); +} + +template struct get_type_impl; + +template struct get_type_impl> { + using type = + remove_cvref_t(std::declval()...))>; +}; + +template +using get_type = typename get_type_impl::type; + +template struct is_compiled_format : std::false_type {}; + +template struct text { + basic_string_view data; + using char_type = Char; + + template + constexpr auto format(OutputIt out, const T&...) const -> OutputIt { + return write(out, data); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr auto make_text(basic_string_view s, size_t pos, size_t size) + -> text { + return {{&s[pos], size}}; +} + +template struct code_unit { + Char value; + using char_type = Char; + + template + constexpr auto format(OutputIt out, const T&...) const -> OutputIt { + *out++ = value; + return out; + } +}; + +// This ensures that the argument type is convertible to `const T&`. +template +constexpr auto get_arg_checked(const Args&... args) -> const T& { + const auto& arg = detail::get(args...); + if constexpr (detail::is_named_arg>()) { + return arg.value; + } else { + return arg; + } +} + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N. +template struct field { + using char_type = Char; + + template + constexpr auto format(OutputIt out, const T&... args) const -> OutputIt { + const V& arg = get_arg_checked(args...); + if constexpr (std::is_convertible>::value) { + auto s = basic_string_view(arg); + return copy(s.begin(), s.end(), out); + } else { + return write(out, arg); + } + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument with name. +template struct runtime_named_field { + using char_type = Char; + basic_string_view name; + + template + constexpr static auto try_format_argument( + OutputIt& out, + // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 + [[maybe_unused]] basic_string_view arg_name, const T& arg) -> bool { + if constexpr (is_named_arg::type>::value) { + if (arg_name == arg.name) { + out = write(out, arg.value); + return true; + } + } + return false; + } + + template + constexpr auto format(OutputIt out, const T&... args) const -> OutputIt { + bool found = (try_format_argument(out, name, args) || ...); + if (!found) { + FMT_THROW(format_error("argument with specified name is not found")); + } + return out; + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +// A replacement field that refers to argument N and has format specifiers. +template struct spec_field { + using char_type = Char; + formatter fmt; + + template + constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const + -> OutputIt { + const auto& vargs = + fmt::make_format_args>(args...); + basic_format_context ctx(out, vargs); + return fmt.format(get_arg_checked(args...), ctx); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template struct concat { + L lhs; + R rhs; + using char_type = typename L::char_type; + + template + constexpr auto format(OutputIt out, const T&... args) const -> OutputIt { + out = lhs.format(out, args...); + return rhs.format(out, args...); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + +template +constexpr auto make_concat(L lhs, R rhs) -> concat { + return {lhs, rhs}; +} + +struct unknown_format {}; + +template +constexpr auto parse_text(basic_string_view str, size_t pos) -> size_t { + for (size_t size = str.size(); pos != size; ++pos) { + if (str[pos] == '{' || str[pos] == '}') break; + } + return pos; +} + +template +constexpr auto compile_format_string(S fmt); + +template +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); + if constexpr (std::is_same, + unknown_format>()) + return tail; + else + return make_concat(head, tail); + } else { + return head; + } +} + +template struct parse_specs_result { + formatter fmt; + size_t end; + int next_arg_id; +}; + +enum { manual_indexing_id = -1 }; + +template +constexpr auto parse_specs(basic_string_view str, size_t pos, + int next_arg_id) -> parse_specs_result { + str.remove_prefix(pos); + auto ctx = + compile_parse_context(str, max_value(), nullptr, next_arg_id); + auto f = formatter(); + auto end = f.parse(ctx); + return {f, pos + fmt::detail::to_unsigned(end - str.data()), + next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; +} + +template struct arg_id_handler { + arg_id_kind kind; + arg_ref arg_id; + + constexpr auto on_auto() -> int { + FMT_ASSERT(false, "handler cannot be used with automatic indexing"); + return 0; + } + constexpr auto on_index(int id) -> int { + kind = arg_id_kind::index; + arg_id = arg_ref(id); + return 0; + } + constexpr auto on_name(basic_string_view id) -> int { + kind = arg_id_kind::name; + arg_id = arg_ref(id); + return 0; + } +}; + +template struct parse_arg_id_result { + arg_id_kind kind; + arg_ref arg_id; + const Char* arg_id_end; +}; + +template +constexpr auto parse_arg_id(const Char* begin, const Char* end) { + auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; + auto arg_id_end = parse_arg_id(begin, end, handler); + return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; +} + +template struct field_type { + using type = remove_cvref_t; +}; + +template +struct field_type::value>> { + using type = remove_cvref_t; +}; + +template +constexpr auto parse_replacement_field_then_tail(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); + if constexpr (c == '}') { + return parse_tail( + field::type, ARG_INDEX>(), fmt); + } else if constexpr (c != ':') { + FMT_THROW(format_error("expected ':'")); + } else { + constexpr auto result = parse_specs::type>( + str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); + if constexpr (result.end >= str.size() || str[result.end] != '}') { + FMT_THROW(format_error("expected '}'")); + return 0; + } else { + return parse_tail( + spec_field::type, ARG_INDEX>{ + result.fmt}, + fmt); + } + } +} + +// Compiles a non-empty format string and returns the compiled representation +// or unknown_format() on unrecognized input. +template +constexpr auto compile_format_string(S fmt) { + using char_type = typename S::char_type; + constexpr auto str = basic_string_view(fmt); + if constexpr (str[POS] == '{') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '{' in format string")); + if constexpr (str[POS + 1] == '{') { + return parse_tail(make_text(str, POS, 1), fmt); + } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { + static_assert(ID != manual_indexing_id, + "cannot switch from manual to automatic argument indexing"); + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail, Args, + POS + 1, ID, next_id>(fmt); + } else { + constexpr auto arg_id_result = + parse_arg_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.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.index; + return parse_replacement_field_then_tail, + Args, arg_id_end_pos, + arg_index, manual_indexing_id>( + fmt); + } else if constexpr (arg_id_result.kind == arg_id_kind::name) { + constexpr auto arg_index = + get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); + if constexpr (arg_index >= 0) { + constexpr auto next_id = + ID != manual_indexing_id ? ID + 1 : manual_indexing_id; + return parse_replacement_field_then_tail< + decltype(get_type::value), Args, arg_id_end_pos, + arg_index, next_id>(fmt); + } else if constexpr (c == '}') { + return parse_tail( + runtime_named_field{arg_id_result.arg_id.name}, fmt); + } else if constexpr (c == ':') { + return unknown_format(); // no type info for specs parsing + } + } + } + } else if constexpr (str[POS] == '}') { + if constexpr (POS + 1 == str.size()) + FMT_THROW(format_error("unmatched '}' in format string")); + return parse_tail(make_text(str, POS, 1), fmt); + } else { + constexpr auto end = parse_text(str, POS + 1); + if constexpr (end - POS > 1) { + return parse_tail(make_text(str, POS, end - POS), fmt); + } else { + return parse_tail(code_unit{str[POS]}, fmt); + } + } +} + +template ::value)> +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); + if constexpr (str.size() == 0) { + return detail::make_text(str, 0, 0); + } else { + constexpr auto result = + detail::compile_format_string, 0, 0>(fmt); + return result; + } +} +#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) +} // namespace detail + +FMT_BEGIN_EXPORT + +#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) + +template ::value)> +FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf, + const T&... args) + -> std::basic_string { + auto s = std::basic_string(); + cf.format(std::back_inserter(s), args...); + return s; +} + +template ::value)> +constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf, + const T&... args) -> OutputIt { + return cf.format(out, args...); +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args) + -> std::basic_string { + if constexpr (std::is_same::value) { + constexpr auto str = basic_string_view(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>::value) { + return fmt::to_string(first.value); + } else { + return fmt::to_string(first); + } + } + } + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format( + static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format(compiled, std::forward(args)...); + } +} + +template ::value)> +FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt { + constexpr auto compiled = detail::compile(S()); + if constexpr (std::is_same, + detail::unknown_format>()) { + return fmt::format_to( + out, static_cast>(S()), + std::forward(args)...); + } else { + return fmt::format_to(out, compiled, std::forward(args)...); + } +} +#endif + +template ::value)> +auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); + return {buf.out(), buf.count()}; +} + +template ::value)> +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + fmt::format_to(appender(buf), fmt, std::forward(args)...); + return buf.count(); +} + +template ::value)> +void print(std::FILE* f, const S& fmt, T&&... args) { + auto buf = memory_buffer(); + fmt::format_to(appender(buf), fmt, std::forward(args)...); + detail::print(f, {buf.data(), buf.size()}); +} + +template ::value)> +void print(const S& fmt, T&&... args) { + print(stdout, fmt, std::forward(args)...); +} + +template class static_format_result { + private: + char data[N]; + + public: + template ::value)> + explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) { + *fmt::format_to(data, fmt, std::forward(args)...) = '\0'; + } + + auto str() const -> fmt::string_view { return {data, N - 1}; } + auto c_str() const -> const char* { return data; } +}; + +/** + * Formats arguments according to the format string `fmt_str` and produces + * a string of the exact required size at compile time. Both the format string + * and the arguments must be compile-time expressions. + * + * The resulting string can be accessed as a C string via `c_str()` or as + * a `fmt::string_view` via `str()`. + * + * **Example**: + * + * // Produces the static string "42" at compile time. + * static constexpr auto result = FMT_STATIC_FORMAT("{}", 42); + * const char* s = result.c_str(); + */ +#define FMT_STATIC_FORMAT(fmt_str, ...) \ + fmt::static_format_result< \ + fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \ + FMT_COMPILE(fmt_str), __VA_ARGS__) + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_COMPILE_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/core.h b/lib/spdlog/include/spdlog/fmt/bundled/core.h new file mode 100644 index 0000000..8ca735f --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/core.h @@ -0,0 +1,5 @@ +// This file is only provided for compatibility and may be removed in future +// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h +// otherwise. + +#include "format.h" diff --git a/lib/spdlog/include/spdlog/fmt/bundled/fmt.license.rst b/lib/spdlog/include/spdlog/fmt/bundled/fmt.license.rst new file mode 100644 index 0000000..1cd1ef9 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/fmt.license.rst @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + +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. diff --git a/lib/spdlog/include/spdlog/fmt/bundled/format-inl.h b/lib/spdlog/include/spdlog/fmt/bundled/format-inl.h new file mode 100644 index 0000000..945cb91 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/format-inl.h @@ -0,0 +1,1948 @@ +// Formatting library for C++ - implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#ifndef FMT_MODULE +# include +# include // errno +# include +# include +# include +#endif + +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) +# include // _isatty +#endif + +#include "format.h" + +#if FMT_USE_LOCALE && !defined(FMT_MODULE) +# include +#endif + +#ifndef FMT_FUNC +# define FMT_FUNC +#endif + +FMT_BEGIN_NAMESPACE + +#ifndef FMT_CUSTOM_ASSERT_FAIL +FMT_FUNC void assert_fail(const char* file, int line, const char* message) { + // Use unchecked std::fprintf to avoid triggering another assertion when + // writing to stderr fails. + std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + abort(); +} +#endif + +#if FMT_USE_LOCALE +namespace detail { +using std::locale; +using std::numpunct; +using std::use_facet; +} // namespace detail +#else +namespace detail { +struct locale {}; +template struct numpunct { + auto grouping() const -> std::string { return "\03"; } + auto thousands_sep() const -> Char { return ','; } + auto decimal_point() const -> Char { return '.'; } +}; +template Facet use_facet(locale) { return {}; } +} // namespace detail +#endif // FMT_USE_LOCALE + +template auto locale_ref::get() const -> Locale { + using namespace detail; + static_assert(std::is_same::value, ""); +#if FMT_USE_LOCALE + if (locale_) return *static_cast(locale_); +#endif + return locale(); +} + +namespace detail { + +FMT_FUNC void format_error_code(detail::buffer& out, int error_code, + string_view message) noexcept { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.try_resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + auto abs_value = static_cast>(error_code); + if (detail::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = appender(out); + if (message.size() <= inline_buffer_size - error_code_size) + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + FMT_ASSERT(out.size() <= inline_buffer_size, ""); +} + +FMT_FUNC void do_report_error(format_func func, int error_code, + const char* message) noexcept { + memory_buffer full_message; + func(full_message, error_code, message); + // Don't use fwrite_all because the latter may throw. + if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) + std::fputc('\n', stderr); +} + +// A wrapper around fwrite that throws on error. +inline void fwrite_all(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); + if (written < count) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +template +FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { + auto&& facet = use_facet>(loc.get()); + auto grouping = facet.grouping(); + auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); + return {std::move(grouping), thousands_sep}; +} +template +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { + return use_facet>(loc.get()).decimal_point(); +} + +#if FMT_USE_LOCALE +FMT_FUNC auto write_loc(appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { + auto locale = loc.get(); + // We cannot use the num_put facet because it may produce output in + // a wrong encoding. + using facet = format_facet; + if (std::has_facet(locale)) + return use_facet(locale).put(out, value, specs); + return facet(locale).put(out, value, specs); +} +#endif +} // namespace detail + +FMT_FUNC void report_error(const char* message) { +#if FMT_MSC_VERSION || defined(__NVCC__) + // Silence unreachable code warnings in MSVC and NVCC because these + // are nearly impossible to fix in a generic code. + volatile bool b = true; + if (!b) return; +#endif + FMT_THROW(format_error(message)); +} + +template typename Locale::id format_facet::id; + +template format_facet::format_facet(Locale& loc) { + auto& np = detail::use_facet>(loc); + grouping_ = np.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); +} + +#if FMT_USE_LOCALE +template <> +FMT_API FMT_FUNC auto format_facet::do_put( + appender out, loc_value val, const format_specs& specs) const -> bool { + return val.visit( + detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); +} +#endif + +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { + auto ec = std::error_code(error_code, std::generic_category()); + return std::system_error(ec, vformat(fmt, args)); +} + +namespace detail { + +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { + return x.f == y.f && x.e == y.e; +} + +// Compilers should be able to optimize this into the ror instruction. +FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { + r &= 31; + return (n >> r) | (n << (32 - r)); +} +FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { + r &= 63; + return (n >> r) | (n << (64 - r)); +} + +// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. +namespace dragonbox { +// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return umul128_upper64(static_cast(x) << 32, y); +} + +// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint64_t high = x * y.high(); + uint128_fallback high_low = umul128(x, y.low()); + return {high + high_low.high(), high_low.low()}; +} + +// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a +// 64-bit unsigned integer. +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { + return x * y; +} + +// Various fast log computations. +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { + FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); + return (e * 631305 - 261663) >> 21; +} + +FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct { + uint32_t divisor; + int shift_amount; +} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; + +// Replaces n by floor(n / pow(10, N)) returning true if and only if n is +// divisible by pow(10, N). +// Precondition: n <= pow(10, N + 1). +template +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { + // The numbers below are chosen such that: + // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, + // 2. nm mod 2^k < m if and only if n is divisible by d, + // where m is magic_number, k is shift_amount + // and d is divisor. + // + // Item 1 is a common technique of replacing division by a constant with + // multiplication, see e.g. "Division by Invariant Integers Using + // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set + // to ceil(2^k/d) for large enough k. + // The idea for item 2 originates from Schubfach. + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + n *= magic_number; + const uint32_t comparison_mask = (1u << info.shift_amount) - 1; + bool result = (n & comparison_mask) < magic_number; + n >>= info.shift_amount; + return result; +} + +// Computes floor(n / pow(10, N)) for small n and N. +// Precondition: n <= pow(10, N + 1). +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { + constexpr auto info = div_small_pow10_infos[N - 1]; + FMT_ASSERT(n <= info.divisor * 10, "n is too large"); + constexpr uint32_t magic_number = + (1u << info.shift_amount) / info.divisor + 1; + return (n * magic_number) >> info.shift_amount; +} + +// Computes floor(n / 10^(kappa + 1)) (float) +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { + // 1374389535 = ceil(2^37/100) + return static_cast((static_cast(n) * 1374389535) >> 37); +} +// Computes floor(n / 10^(kappa + 1)) (double) +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { + // 2361183241434822607 = ceil(2^(64+7)/1000) + return umul128_upper64(n, 2361183241434822607ull) >> 7; +} + +// Various subroutines using pow10 cache +template struct cache_accessor; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint64_t; + + static auto get_cached_power(int k) noexcept -> uint64_t { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + static constexpr uint64_t pow10_significands[] = { + 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, + 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, + 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, + 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, + 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, + 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, + 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, + 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, + 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, + 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, + 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, + 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, + 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, + 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, + 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, + 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, + 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, + 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, + 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, + 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, + 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, + 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, + 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, + 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, + 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, + 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; + return pow10_significands[k - float_info::min_k]; + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul96_upper64(u, cache); + return {static_cast(r >> 32), + static_cast(r) == 0}; + } + + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul96_lower64(two_f, cache); + return {((r >> (64 - beta)) & 1) != 0, + static_cast(r >> (32 - beta)) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache - (cache >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return static_cast( + (cache + (cache >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta)); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (static_cast( + cache >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +template <> struct cache_accessor { + using carrier_uint = float_info::carrier_uint; + using cache_entry_type = uint128_fallback; + + static auto get_cached_power(int k) noexcept -> uint128_fallback { + FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, + "k is out of range"); + + static constexpr uint128_fallback pow10_significands[] = { +#if FMT_USE_FULL_CACHE_DRAGONBOX + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0x9faacf3df73609b1, 0x77b191618c54e9ad}, + {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, + {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, + {0x9becce62836ac577, 0x4ee367f9430aec33}, + {0xc2e801fb244576d5, 0x229c41f793cda740}, + {0xf3a20279ed56d48a, 0x6b43527578c11110}, + {0x9845418c345644d6, 0x830a13896b78aaaa}, + {0xbe5691ef416bd60c, 0x23cc986bc656d554}, + {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, + {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, + {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, + {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, + {0x91376c36d99995be, 0x23100809b9c21fa2}, + {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, + {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, + {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, + {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, + {0xdd95317f31c7fa1d, 0x40405643d711d584}, + {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, + {0xad1c8eab5ee43b66, 0xda3243650005eed0}, + {0xd863b256369d4a40, 0x90bed43e40076a83}, + {0x873e4f75e2224e68, 0x5a7744a6e804a292}, + {0xa90de3535aaae202, 0x711515d0a205cb37}, + {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, + {0x8412d9991ed58091, 0xe858790afe9486c3}, + {0xa5178fff668ae0b6, 0x626e974dbe39a873}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, + {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, + {0xc987434744ac874e, 0xa327ffb266b56221}, + {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, + {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, + {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, + {0xf6019da07f549b2b, 0x7e2a53a146606a49}, + {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, + {0xc0314325637a1939, 0xfa911155fefb5309}, + {0xf03d93eebc589f88, 0x793555ab7eba27cb}, + {0x96267c7535b763b5, 0x4bc1558b2f3458df}, + {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, + {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, + {0x92a1958a7675175f, 0x0bfacd89ec191eca}, + {0xb749faed14125d36, 0xcef980ec671f667c}, + {0xe51c79a85916f484, 0x82b7e12780e7401b}, + {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, + {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, + {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, + {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, + {0xaecc49914078536d, 0x58fae9f773886e19}, + {0xda7f5bf590966848, 0xaf39a475506a899f}, + {0x888f99797a5e012d, 0x6d8406c952429604}, + {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, + {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, + {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0xd0601d8efc57b08b, 0xf13b94daf124da27}, + {0x823c12795db6ce57, 0x76c53d08d6b70859}, + {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, + {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, + {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, + {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, + {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, + {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, + {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, + {0xc21094364dfb5636, 0x985915fc12f542e5}, + {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, + {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, + {0xbd8430bd08277231, 0x50c6ff782a838354}, + {0xece53cec4a314ebd, 0xa4f8bf5635246429}, + {0x940f4613ae5ed136, 0x871b7795e136be9a}, + {0xb913179899f68584, 0x28e2557b59846e40}, + {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, + {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, + {0xb4bca50b065abe63, 0x0fed077a756b53aa}, + {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, + {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, + {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, + {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, + {0x89e42caaf9491b60, 0xf41686c49db57245}, + {0xac5d37d5b79b6239, 0x311c2875c522ced6}, + {0xd77485cb25823ac7, 0x7d633293366b828c}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, + {0xd267caa862a12d66, 0xd072df63c324fd7c}, + {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, + {0xa46116538d0deb78, 0x52d9be85f074e609}, + {0xcd795be870516656, 0x67902e276c921f8c}, + {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, + {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, + {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, + {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, + {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, + {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, + {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, + {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, + {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, + {0xef340a98172aace4, 0x86fb897116c87c35}, + {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, + {0xbae0a846d2195712, 0x8974836059cca10a}, + {0xe998d258869facd7, 0x2bd1a438703fc94c}, + {0x91ff83775423cc06, 0x7b6306a34627ddd0}, + {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, + {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, + {0x8e938662882af53e, 0x547eb47b7282ee9d}, + {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, + {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, + {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, + {0xae0b158b4738705e, 0x9624ab50b148d446}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, + {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, + {0xd47487cc8470652b, 0x7647c32000696720}, + {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, + {0xa5fb0a17c777cf09, 0xf468107100525891}, + {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, + {0x81ac1fe293d599bf, 0xc6f14cd848405531}, + {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, + {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, + {0xfd442e4688bd304a, 0x908f4a166d1da664}, + {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, + {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, + {0xf7549530e188c128, 0xd12bee59e68ef47d}, + {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, + {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, + {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, + {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, + {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, + {0xebdf661791d60f56, 0x111b495b3464ad22}, + {0x936b9fcebb25c995, 0xcab10dd900beec35}, + {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, + {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, + {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, + {0xb3f4e093db73a093, 0x59ed216765690f57}, + {0xe0f218b8d25088b8, 0x306869c13ec3532d}, + {0x8c974f7383725573, 0x1e414218c73a13fc}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, + {0x894bc396ce5da772, 0x6b8bba8c328eb784}, + {0xab9eb47c81f5114f, 0x066ea92f3f326565}, + {0xd686619ba27255a2, 0xc80a537b0efefebe}, + {0x8613fd0145877585, 0xbd06742ce95f5f37}, + {0xa798fc4196e952e7, 0x2c48113823b73705}, + {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, + {0x82ef85133de648c4, 0x9a984d73dbe722fc}, + {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, + {0xcc963fee10b7d1b3, 0x318df905079926a9}, + {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, + {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, + {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, + {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, + {0x9c1661a651213e2d, 0x06bea10ca65c084f}, + {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, + {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, + {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, + {0xbe89523386091465, 0xf6bbb397f1135824}, + {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, + {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, + {0xba121a4650e4ddeb, 0x92f34d62616ce414}, + {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, + {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, + {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, + {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, + {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, + {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, + {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, + {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, + {0x87625f056c7c4a8b, 0x11471cd764ad4973}, + {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, + {0xd389b47879823479, 0x4aff1d108d4ec2c4}, + {0x843610cb4bf160cb, 0xcedf722a585139bb}, + {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, + {0xce947a3da6a9273e, 0x733d226229feea33}, + {0x811ccc668829b887, 0x0806357d5a3f5260}, + {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, + {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, + {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, + {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, + {0xc5029163f384a931, 0x0a9e795e65d4df12}, + {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, + {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, + {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, + {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, + {0x964e858c91ba2655, 0x3a6a07f8d510f870}, + {0xbbe226efb628afea, 0x890489f70a55368c}, + {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, + {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, + {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, + {0xb32df8e9f3546564, 0x47939822dc96abfa}, + {0xdff9772470297ebd, 0x59787e2b93bc56f8}, + {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, + {0xaefae51477a06b03, 0xede622920b6b23f2}, + {0xdab99e59958885c4, 0xe95fab368e45ecee}, + {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, + {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, + {0xd59944a37c0752a2, 0x4be76d3346f04960}, + {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, + {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, + {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, + {0x825ecc24c873782f, 0x8ed400668c0c28c9}, + {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, + {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, + {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, + {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, + {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, + {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, + {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, + {0xc24452da229b021b, 0xfbe85badce996169}, + {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, + {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, + {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, + {0xed246723473e3813, 0x290123e9aab23b69}, + {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, + {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, + {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, + {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, + {0x8d590723948a535f, 0x579c487e5a38ad0f}, + {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, + {0xdcdb1b2798182244, 0xf8e431456cf88e66}, + {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, + {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, + {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, + {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, + {0xa87fea27a539e9a5, 0x3f2398d747b36225}, + {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, + {0x83a3eeeef9153e89, 0x1953cf68300424ad}, + {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, + {0xcdb02555653131b6, 0x3792f412cb06794e}, + {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, + {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, + {0xc8de047564d20a8b, 0xf245825a5a445276}, + {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, + {0x9ced737bb6c4183d, 0x55464dd69685606c}, + {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, + {0xf53304714d9265df, 0xd53dd99f4b3066a9}, + {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, + {0xbf8fdb78849a5f96, 0xde98520472bdd034}, + {0xef73d256a5c0f77c, 0x963e66858f6d4441}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xbb127c53b17ec159, 0x5560c018580d5d53}, + {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, + {0x9226712162ab070d, 0xcab3961304ca70e9}, + {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, + {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, + {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, + {0xb267ed1940f1c61c, 0x55f038b237591ed4}, + {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, + {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, + {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, + {0xd9c7dced53c72255, 0x96e7bd358c904a22}, + {0x881cea14545c7575, 0x7e50d64177da2e55}, + {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, + {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, + {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, + {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, + {0xcfb11ead453994ba, 0x67de18eda5814af3}, + {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, + {0xa2425ff75e14fc31, 0xa1258379a94d028e}, + {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, + {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, + {0x9e74d1b791e07e48, 0x775ea264cf55347e}, + {0xc612062576589dda, 0x95364afe032a819e}, + {0xf79687aed3eec551, 0x3a83ddbd83f52205}, + {0x9abe14cd44753b52, 0xc4926a9672793543}, + {0xc16d9a0095928a27, 0x75b7053c0f178294}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, + {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, + {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, + {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, + {0xb877aa3236a4b449, 0x09befeb9fad487c3}, + {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, + {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, + {0xb424dc35095cd80f, 0x538484c19ef38c95}, + {0xe12e13424bb40e13, 0x2865a5f206b06fba}, + {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, + {0xafebff0bcb24aafe, 0xf78f69a51539d749}, + {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, + {0x89705f4136b4a597, 0x31680a88f8953031}, + {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, + {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, + {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, + {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, + {0xd1b71758e219652b, 0xd3c36113404ea4a9}, + {0x83126e978d4fdf3b, 0x645a1cac083126ea}, + {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, + {0xcccccccccccccccc, 0xcccccccccccccccd}, + {0x8000000000000000, 0x0000000000000000}, + {0xa000000000000000, 0x0000000000000000}, + {0xc800000000000000, 0x0000000000000000}, + {0xfa00000000000000, 0x0000000000000000}, + {0x9c40000000000000, 0x0000000000000000}, + {0xc350000000000000, 0x0000000000000000}, + {0xf424000000000000, 0x0000000000000000}, + {0x9896800000000000, 0x0000000000000000}, + {0xbebc200000000000, 0x0000000000000000}, + {0xee6b280000000000, 0x0000000000000000}, + {0x9502f90000000000, 0x0000000000000000}, + {0xba43b74000000000, 0x0000000000000000}, + {0xe8d4a51000000000, 0x0000000000000000}, + {0x9184e72a00000000, 0x0000000000000000}, + {0xb5e620f480000000, 0x0000000000000000}, + {0xe35fa931a0000000, 0x0000000000000000}, + {0x8e1bc9bf04000000, 0x0000000000000000}, + {0xb1a2bc2ec5000000, 0x0000000000000000}, + {0xde0b6b3a76400000, 0x0000000000000000}, + {0x8ac7230489e80000, 0x0000000000000000}, + {0xad78ebc5ac620000, 0x0000000000000000}, + {0xd8d726b7177a8000, 0x0000000000000000}, + {0x878678326eac9000, 0x0000000000000000}, + {0xa968163f0a57b400, 0x0000000000000000}, + {0xd3c21bcecceda100, 0x0000000000000000}, + {0x84595161401484a0, 0x0000000000000000}, + {0xa56fa5b99019a5c8, 0x0000000000000000}, + {0xcecb8f27f4200f3a, 0x0000000000000000}, + {0x813f3978f8940984, 0x4000000000000000}, + {0xa18f07d736b90be5, 0x5000000000000000}, + {0xc9f2c9cd04674ede, 0xa400000000000000}, + {0xfc6f7c4045812296, 0x4d00000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xc5371912364ce305, 0x6c28000000000000}, + {0xf684df56c3e01bc6, 0xc732000000000000}, + {0x9a130b963a6c115c, 0x3c7f400000000000}, + {0xc097ce7bc90715b3, 0x4b9f100000000000}, + {0xf0bdc21abb48db20, 0x1e86d40000000000}, + {0x96769950b50d88f4, 0x1314448000000000}, + {0xbc143fa4e250eb31, 0x17d955a000000000}, + {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, + {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, + {0xb7abc627050305ad, 0xf14a3d9e40000000}, + {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, + {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, + {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, + {0xe0352f62a19e306e, 0xd50b2037ad200000}, + {0x8c213d9da502de45, 0x4526f422cc340000}, + {0xaf298d050e4395d6, 0x9670b12b7f410000}, + {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, + {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, + {0xab0e93b6efee0053, 0x8eea0d047a457a00}, + {0xd5d238a4abe98068, 0x72a4904598d6d880}, + {0x85a36366eb71f041, 0x47a6da2b7f864750}, + {0xa70c3c40a64e6c51, 0x999090b65f67d924}, + {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, + {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, + {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, + {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0x9f4f2726179a2245, 0x01d762422c946591}, + {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, + {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, + {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, + {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, + {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, + {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, + {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, + {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, + {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, + {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, + {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, + {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, + {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, + {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, + {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, + {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, + {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, + {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, + {0xacb92ed9397bf996, 0x49c2c37f07965405}, + {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, + {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, + {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, + {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, + {0x83c7088e1aab65db, 0x792667c6da79e0fb}, + {0xa4b8cab1a1563f52, 0x577001b891185939}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0x80b05e5ac60b6178, 0x544f8158315b05b5}, + {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, + {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, + {0xfb5878494ace3a5f, 0x04ab48a04065c724}, + {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, + {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, + {0xf5746577930d6500, 0xca8f44ec7ee3647a}, + {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, + {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, + {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, + {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, + {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, + {0xea1575143cf97226, 0xf52d09d71a3293be}, + {0x924d692ca61be758, 0x593c2626705f9c57}, + {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, + {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, + {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, + {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, + {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, + {0x8b865b215899f46c, 0xbd79e0d20082ee75}, + {0xae67f1e9aec07187, 0xecd8590680a3aa12}, + {0xda01ee641a708de9, 0xe80e6f4820cc9496}, + {0x884134fe908658b2, 0x3109058d147fdcde}, + {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, + {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, + {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0xcfe87f7cef46ff16, 0xe612641865679a64}, + {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, + {0xa26da3999aef7749, 0xe3be5e330f38f09e}, + {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, + {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, + {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, + {0xc646d63501a1511d, 0xb281e1fd541501b9}, + {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, + {0x9ae757596946075f, 0x3375788de9b06959}, + {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, + {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, + {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, + {0xbd176620a501fbff, 0xb650e5a93bc3d899}, + {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, + {0x93ba47c980e98cdf, 0xc66f336c36b10138}, + {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, + {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, + {0x9043ea1ac7e41392, 0x87c89837ad68db30}, + {0xb454e4a179dd1877, 0x29babe4598c311fc}, + {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, + {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, + {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, + {0xdc21a1171d42645d, 0x76707543f4fa1f74}, + {0x899504ae72497eba, 0x6a06494a791c53a9}, + {0xabfa45da0edbde69, 0x0487db9d17636893}, + {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xa7f26836f282b732, 0x8e6cac7768d7141f}, + {0xd1ef0244af2364ff, 0x3207d795430cd927}, + {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, + {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, + {0xcd036837130890a1, 0x36dba887c37a8c10}, + {0x802221226be55a64, 0xc2494954da2c978a}, + {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, + {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, + {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, + {0x9c69a97284b578d7, 0xff2a760414536efc}, + {0xc38413cf25e2d70d, 0xfef5138519684abb}, + {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, + {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, + {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, + {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, + {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, + {0xba756174393d88df, 0x94f971119aeef9e5}, + {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, + {0x91abb422ccb812ee, 0xac62e055c10ab33b}, + {0xb616a12b7fe617aa, 0x577b986b314d600a}, + {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, + {0x8e41ade9fbebc27d, 0x14588f13be847308}, + {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, + {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, + {0x8aec23d680043bee, 0x25de7bb9480d5855}, + {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0x87aa9aff79042286, 0x90fb44d2f05d0843}, + {0xa99541bf57452b28, 0x353a1607ac744a54}, + {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, + {0x847c9b5d7c2e09b7, 0x69956135febada12}, + {0xa59bc234db398c25, 0x43fab9837e699096}, + {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, + {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, + {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, + {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, + {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, + {0x9defbf01b061adab, 0x3a0888136afa64a8}, + {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, + {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, + {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, + {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, + {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, + {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, + {0xbc4665b596706114, 0x873d5d9f0dde1fef}, + {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, + {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, + {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, + {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, + {0x8fa475791a569d10, 0xf96e017d694487bd}, + {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, + {0xe070f78d3927556a, 0x85bbe253f47b1418}, + {0x8c469ab843b89562, 0x93956d7478ccec8f}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, + {0x88fcf317f22241e2, 0x441fece3bdf81f04}, + {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, + {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, + {0x85c7056562757456, 0xf6872d5667844e4a}, + {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, + {0xd106f86e69d785c7, 0xe13336d701beba53}, + {0x82a45b450226b39c, 0xecc0024661173474}, + {0xa34d721642b06084, 0x27f002d7f95d0191}, + {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, + {0xff290242c83396ce, 0x7e67047175a15272}, + {0x9f79a169bd203e41, 0x0f0062c6e984d387}, + {0xc75809c42c684dd1, 0x52c07b78a3e60869}, + {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, + {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, + {0xc2abf989935ddbfe, 0x6acff893d00ea436}, + {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, + {0x98165af37b2153de, 0xc3727a337a8b704b}, + {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, + {0xeda2ee1c7064130c, 0x1162def06f79df74}, + {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, + {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, + {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, + {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, + {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, + {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xb10d8e1456105dad, 0x7425a83e872c5f48}, + {0xdd50f1996b947518, 0xd12f124e28f7771a}, + {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, + {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, + {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, + {0x8714a775e3e95c78, 0x65acfaec34810a72}, + {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, + {0xd31045a8341ca07c, 0x1ede48111209a051}, + {0x83ea2b892091e44d, 0x934aed0aab460433}, + {0xa4e4b66b68b65d60, 0xf81da84d56178540}, + {0xce1de40642e3f4b9, 0x36251260ab9d668f}, + {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, + {0xa1075a24e4421730, 0xb24cf65b8612f820}, + {0xc94930ae1d529cfc, 0xdee033f26797b628}, + {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, + {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, + {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, + {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, + {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, + {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, + {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, + {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, + {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, + {0xea53df5fd18d5513, 0x84c86189216dc5ee}, + {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, + {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, + {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, + {0xdf78e4b2bd342cf6, 0x914da9246b255417}, + {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, + {0xae9672aba3d0c320, 0xa184ac2473b529b2}, + {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, + {0x8865899617fb1871, 0x7e2fa67c7a658893}, + {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, + {0xd51ea6fa85785631, 0x552a74227f3ea566}, + {0x8533285c936b35de, 0xd53a88958f872760}, + {0xa67ff273b8460356, 0x8a892abaf368f138}, + {0xd01fef10a657842c, 0x2d2b7569b0432d86}, + {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, + {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, + {0xcb3f2f7642717713, 0x241c70a936219a74}, + {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, + {0x9ec95d1463e8a506, 0xf4363804324a40ab}, + {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, + {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, + {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, + {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, + {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, + {0x976e41088617ca01, 0xd5be0503e085d814}, + {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, + {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, + {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, + {0x906a617d450187e2, 0x27fb2b80668b24c6}, + {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, + {0xe1a63853bbd26451, 0x5e7873f8a0396974}, + {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, + {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, + {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, + {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, + {0xac2820d9623bf429, 0x546345fa9fbdcd45}, + {0xd732290fbacaf133, 0xa97c177947ad4096}, + {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, + {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, + {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, + {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, + {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, + {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, + {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, + {0xa0555e361951c366, 0xd7e105bcc3326220}, + {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, + {0xfa856334878fc150, 0xb14f98f6f0feb952}, + {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, + {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, + {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, + {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, + {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, + {0xeeea5d5004981478, 0x1858ccfce06cac75}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xbaa718e68396cffd, 0xd30560258f54e6bb}, + {0xe950df20247c83fd, 0x47c6b82ef32a206a}, + {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, + {0xb6472e511c81471d, 0xe0133fe4adf8e953}, + {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, + {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, + {0xb201833b35d63f73, 0x2cd2cc6551e513db}, + {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, + {0x8b112e86420f6191, 0xfb04afaf27faf783}, + {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, + {0xd94ad8b1c7380874, 0x18375281ae7822bd}, + {0x87cec76f1c830548, 0x8f2293910d0b15b6}, + {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, + {0xd433179d9c8cb841, 0x5fa60692a46151ec}, + {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, + {0xa5c7ea73224deff3, 0x12b9b522906c0801}, + {0xcf39e50feae16bef, 0xd768226b34870a01}, + {0x81842f29f2cce375, 0xe6a1158300d46641}, + {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, + {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, + {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, + {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, + {0xc5a05277621be293, 0xc7098b7305241886}, + {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, + {0x9a65406d44a5c903, 0x737f74f1dc043329}, + {0xc0fe908895cf3b44, 0x505f522e53053ff3}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, + {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, + {0xbc789925624c5fe0, 0xb67d16413d132073}, + {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, + {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, + {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, + {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, + {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, + {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, + {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, + {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, + {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, +#else + {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, + {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, + {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, + {0x86a8d39ef77164bc, 0xae5dff9c02033198}, + {0xd98ddaee19068c76, 0x3badd624dd9b0958}, + {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, + {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, + {0xe55990879ddcaabd, 0xcc420a6a101d0516}, + {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, + {0x95a8637627989aad, 0xdde7001379a44aa9}, + {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, + {0xc350000000000000, 0x0000000000000000}, + {0x9dc5ada82b70b59d, 0xf020000000000000}, + {0xfee50b7025c36a08, 0x02f236d04753d5b5}, + {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, + {0xa6539930bf6bff45, 0x84db8346b786151d}, + {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, + {0xd910f7ff28069da4, 0x1b2ba1518094da05}, + {0xaf58416654a6babb, 0x387ac8d1970027b3}, + {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, + {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, + {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, + {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, + {0xf13e34aabb430a15, 0x647726b9e7c68ff0} +#endif + }; + +#if FMT_USE_FULL_CACHE_DRAGONBOX + return pow10_significands[k - float_info::min_k]; +#else + static constexpr uint64_t powers_of_5_64[] = { + 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, + 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, + 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, + 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, + 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, + 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, + 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, + 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, + 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; + + static const int compression_ratio = 27; + + // Compute base index. + int cache_index = (k - float_info::min_k) / compression_ratio; + int kb = cache_index * compression_ratio + float_info::min_k; + int offset = k - kb; + + // Get base cache. + uint128_fallback base_cache = pow10_significands[cache_index]; + if (offset == 0) return base_cache; + + // Compute the required amount of bit-shift. + int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; + FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); + + // Try to recover the real cache. + uint64_t pow5 = powers_of_5_64[offset]; + uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); + uint128_fallback middle_low = umul128(base_cache.low(), pow5); + + recovered_cache += middle_low.high(); + + uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); + uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); + + recovered_cache = + uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, + ((middle_low.low() >> alpha) | middle_to_low)}; + FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); + return {recovered_cache.high(), recovered_cache.low() + 1}; +#endif + } + + struct compute_mul_result { + carrier_uint result; + bool is_integer; + }; + struct compute_mul_parity_result { + bool parity; + bool is_integer; + }; + + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { + auto r = umul192_upper128(u, cache); + return {r.high(), r.low() == 0}; + } + + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { + return static_cast(cache.high() >> (64 - 1 - beta)); + } + + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { + FMT_ASSERT(beta >= 1, ""); + FMT_ASSERT(beta < 64, ""); + + auto r = umul192_lower128(two_f, cache); + return {((r.high() >> (64 - beta)) & 1) != 0, + ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; + } + + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() - + (cache.high() >> (num_significand_bits() + 2))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return (cache.high() + + (cache.high() >> (num_significand_bits() + 1))) >> + (64 - num_significand_bits() - 1 - beta); + } + + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { + return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + + 1) / + 2; + } +}; + +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { + return cache_accessor::get_cached_power(k); +} + +// Various integer checks +template +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { + const int case_shorter_interval_left_endpoint_lower_threshold = 2; + const int case_shorter_interval_left_endpoint_upper_threshold = 3; + return exponent >= case_shorter_interval_left_endpoint_lower_threshold && + exponent <= case_shorter_interval_left_endpoint_upper_threshold; +} + +// Remove trailing zeros from n and return the number of zeros removed (float). +FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int { + FMT_ASSERT(n != 0, ""); + // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. + constexpr uint32_t mod_inv_5 = 0xcccccccd; + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + return s; +} + +// Removes trailing zeros and returns the number of zeros removed (double). +FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int { + FMT_ASSERT(n != 0, ""); + + // Is n is divisible by 10^8? + constexpr uint32_t ten_pow_8 = 100000000u; + if ((n % ten_pow_8) == 0) { + // If yes, work with the quotient... + auto n32 = static_cast(n / ten_pow_8); + // ... and use the 32 bit variant of the function + int num_zeros = remove_trailing_zeros(n32, 8); + n = n32; + return num_zeros; + } + + // If n is not divisible by 10^8, work with n itself. + constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 + + int s = 0; + while (true) { + auto q = rotr(n * mod_inv_25, 2); + if (q > max_value() / 100) break; + n = q; + s += 2; + } + auto q = rotr(n * mod_inv_5, 1); + if (q <= max_value() / 10) { + n = q; + s |= 1; + } + + return s; +} + +// The main algorithm for shorter interval case +template +FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp { + decimal_fp ret_value; + // Compute k and beta + const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute xi and zi + using cache_entry_type = typename cache_accessor::cache_entry_type; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + + auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( + cache, beta); + auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( + cache, beta); + + // If the left endpoint is not an integer, increase it + if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; + + // Try bigger divisor + ret_value.significand = zi / 10; + + // If succeed, remove trailing zeros if necessary and return + if (ret_value.significand * 10 >= xi) { + ret_value.exponent = minus_k + 1; + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + } + + // Otherwise, compute the round-up of y + ret_value.significand = + cache_accessor::compute_round_up_for_shorter_interval_case(cache, + beta); + ret_value.exponent = minus_k; + + // When tie occurs, choose one of them according to the rule + if (exponent >= float_info::shorter_interval_tie_lower_threshold && + exponent <= float_info::shorter_interval_tie_upper_threshold) { + ret_value.significand = ret_value.significand % 2 == 0 + ? ret_value.significand + : ret_value.significand - 1; + } else if (ret_value.significand < xi) { + ++ret_value.significand; + } + return ret_value; +} + +template auto to_decimal(T x) noexcept -> decimal_fp { + // Step 1: integer promotion & Schubfach multiplier calculation. + + using carrier_uint = typename float_info::carrier_uint; + using cache_entry_type = typename cache_accessor::cache_entry_type; + auto br = bit_cast(x); + + // Extract significand bits and exponent bits. + const carrier_uint significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + carrier_uint significand = (br & significand_mask); + int exponent = + static_cast((br & exponent_mask()) >> num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + + // Shorter interval case; proceed like Schubfach. + // In fact, when exponent == 1 and significand == 0, the interval is + // regular. However, it can be shown that the end-results are anyway same. + if (significand == 0) return shorter_interval_case(exponent); + + significand |= (static_cast(1) << num_significand_bits()); + } else { + // Subnormal case; the interval is always regular. + if (significand == 0) return {0, 0}; + exponent = + std::numeric_limits::min_exponent - num_significand_bits() - 1; + } + + const bool include_left_endpoint = (significand % 2 == 0); + const bool include_right_endpoint = include_left_endpoint; + + // Compute k and beta. + const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; + const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); + const int beta = exponent + floor_log2_pow10(-minus_k); + + // Compute zi and deltai. + // 10^kappa <= deltai < 10^(kappa + 1) + const uint32_t deltai = cache_accessor::compute_delta(cache, beta); + const carrier_uint two_fc = significand << 1; + + // For the case of binary32, the result of integer check is not correct for + // 29711844 * 2^-82 + // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 + // and 29711844 * 2^-81 + // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, + // and they are the unique counterexamples. However, since 29711844 is even, + // this does not cause any problem for the endpoints calculations; it can only + // cause a problem when we need to perform integer check for the center. + // Fortunately, with these inputs, that branch is never executed, so we are + // fine. + const typename cache_accessor::compute_mul_result z_mul = + cache_accessor::compute_mul((two_fc | 1) << beta, cache); + + // Step 2: Try larger divisor; remove trailing zeros if necessary. + + // Using an upper bound on zi, we might be able to optimize the division + // better than the compiler; we are computing zi / big_divisor here. + decimal_fp ret_value; + ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); + uint32_t r = static_cast(z_mul.result - float_info::big_divisor * + ret_value.significand); + + if (r < deltai) { + // Exclude the right endpoint if necessary. + if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { + --ret_value.significand; + r = float_info::big_divisor; + goto small_divisor_case_label; + } + } else if (r > deltai) { + goto small_divisor_case_label; + } else { + // r == deltai; compare fractional parts. + const typename cache_accessor::compute_mul_parity_result x_mul = + cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); + + if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) + goto small_divisor_case_label; + } + ret_value.exponent = minus_k + float_info::kappa + 1; + + // We may need to remove trailing zeros. + ret_value.exponent += remove_trailing_zeros(ret_value.significand); + return ret_value; + + // Step 3: Find the significand with the smaller divisor. + +small_divisor_case_label: + ret_value.significand *= 10; + ret_value.exponent = minus_k + float_info::kappa; + + uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); + const bool approx_y_parity = + ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; + + // Is dist divisible by 10^kappa? + const bool divisible_by_small_divisor = + check_divisibility_and_divide_by_pow10::kappa>(dist); + + // Add dist / 10^kappa to the significand. + ret_value.significand += dist; + + if (!divisible_by_small_divisor) return ret_value; + + // Check z^(f) >= epsilon^(f). + // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, + // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). + // Since there are only 2 possibilities, we only need to care about the + // parity. Also, zi and r should have the same parity since the divisor + // is an even number. + const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); + + // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), + // or equivalently, when y is an integer. + if (y_mul.parity != approx_y_parity) + --ret_value.significand; + else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) + --ret_value.significand; + return ret_value; +} +} // namespace dragonbox +} // namespace detail + +template <> struct formatter { + FMT_CONSTEXPR auto parse(format_parse_context& ctx) + -> format_parse_context::iterator { + return ctx.begin(); + } + + auto format(const detail::bigint& n, format_context& ctx) const + -> format_context::iterator { + auto out = ctx.out(); + bool first = true; + for (auto i = n.bigits_.size(); i > 0; --i) { + auto value = n.bigits_[i - 1u]; + if (first) { + out = fmt::format_to(out, FMT_STRING("{:x}"), value); + first = false; + continue; + } + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); + } + if (n.exp_ > 0) + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); + return out; + } +}; + +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { + for_each_codepoint(s, [this](uint32_t cp, string_view) { + if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); + if (cp <= 0xFFFF) { + buffer_.push_back(static_cast(cp)); + } else { + cp -= 0x10000; + buffer_.push_back(static_cast(0xD800 + (cp >> 10))); + buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); + } + return true; + }); + buffer_.push_back(0); +} + +FMT_FUNC void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept { + FMT_TRY { + auto ec = std::error_code(error_code, std::generic_category()); + detail::write(appender(out), std::system_error(ec, message).what()); + return; + } + FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +FMT_FUNC void report_system_error(int error_code, + const char* message) noexcept { + do_report_error(format_system_error, error_code, message); +} + +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { + // Don't optimize the "{}" case to keep the binary size small and because it + // can be better optimized in fmt::format anyway. + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + return to_string(buffer); +} + +namespace detail { + +FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc) { + auto out = appender(buf); + if (fmt.size() == 2 && equal2(fmt.data(), "{}")) + return args.get(0).visit(default_arg_formatter{out}); + parse_format_string(fmt, + format_handler<>{parse_context<>(fmt), {out, args, loc}}); +} + +template struct span { + T* data; + size_t size; +}; + +template auto flockfile(F* f) -> decltype(_lock_file(f)) { + _lock_file(f); +} +template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { + _unlock_file(f); +} + +#ifndef getc_unlocked +template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { + return _fgetc_nolock(f); +} +#endif + +template +struct has_flockfile : std::false_type {}; + +template +struct has_flockfile()))>> + : std::true_type {}; + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } + + void flush() { fflush(this->file_); } +}; + +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + auto needs_flush() const -> bool { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + auto size = max_of(this->file_->_IO_write_ptr - end, 0); + return memchr(end, '\n', static_cast(size)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; + } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + auto needs_flush() const -> bool { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +#ifndef FMT_USE_FALLBACK_FILE +# define FMT_USE_FALLBACK_FILE 0 +#endif + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} + +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +template +class file_print_buffer : public buffer { + public: + explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} +}; + +template +class file_print_buffer::value>> + : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + F* f = file_; // Make funlockfile depend on the template parameter F + funlockfile(f); // for the system API detection to work. + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } +#else +using dword = conditional_t; +extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // + void*, const void*, dword, dword*, void*); + +FMT_FUNC bool write_console(int fd, string_view text) { + auto u16 = utf8_to_utf16(text); + return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), + static_cast(u16.size()), nullptr, nullptr) != 0; +} +#endif + +#ifdef _WIN32 +// Print assuming legacy (non-Unicode) encoding. +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_all(buffer.data(), buffer.size(), f); +} +#endif + +FMT_FUNC void print(std::FILE* f, string_view text) { +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_all(text.data(), text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) + return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer<>(f); + return detail::vformat_to(buffer, fmt, args); +} + +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); +} + +FMT_FUNC void vprint(string_view fmt, format_args args) { + vprint(stdout, fmt, args); +} + +namespace detail { + +struct singleton { + unsigned char upper; + unsigned char lower_count; +}; + +inline auto is_printable(uint16_t x, const singleton* singletons, + size_t singletons_size, + const unsigned char* singleton_lowers, + const unsigned char* normal, size_t normal_size) + -> bool { + auto upper = x >> 8; + auto lower_start = 0; + for (size_t i = 0; i < singletons_size; ++i) { + auto s = singletons[i]; + auto lower_end = lower_start + s.lower_count; + if (upper < s.upper) break; + if (upper == s.upper) { + for (auto j = lower_start; j < lower_end; ++j) { + if (singleton_lowers[j] == (x & 0xff)) return false; + } + } + lower_start = lower_end; + } + + auto xsigned = static_cast(x); + auto current = true; + for (size_t i = 0; i < normal_size; ++i) { + auto v = static_cast(normal[i]); + auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; + xsigned -= len; + if (xsigned < 0) break; + current = !current; + } + return current; +} + +// This code is generated by support/printable.py. +FMT_FUNC auto is_printable(uint32_t cp) -> bool { + static constexpr singleton singletons0[] = { + {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, + {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, + {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, + {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, + {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, + {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, + {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, + }; + static constexpr unsigned char singletons0_lower[] = { + 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, + 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, + 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, + 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, + 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, + 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, + 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, + 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, + 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, + 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, + 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, + 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, + 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, + 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, + 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, + 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, + 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, + 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, + 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, + 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, + 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, + 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, + 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, + 0xfe, 0xff, + }; + static constexpr singleton singletons1[] = { + {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, + {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, + {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, + {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, + {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, + {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, + {0xfa, 2}, {0xfb, 1}, + }; + static constexpr unsigned char singletons1_lower[] = { + 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, + 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, + 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, + 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, + 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, + 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, + 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, + 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, + 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, + 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, + 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, + 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, + 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, + 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, + 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, + }; + static constexpr unsigned char normal0[] = { + 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, + 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, + 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, + 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, + 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, + 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, + 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, + 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, + 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, + 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, + 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, + 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, + 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, + 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, + 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, + 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, + 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, + 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, + 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, + 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, + 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, + 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, + 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, + 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, + 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, + 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, + }; + static constexpr unsigned char normal1[] = { + 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, + 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, + 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, + 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, + 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, + 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, + 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, + 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, + 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, + 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, + 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, + 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, + 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, + 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, + 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, + 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, + 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, + 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, + 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, + 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, + 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, + 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, + 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, + 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, + 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, + 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, + 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, + 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, + 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, + 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, + 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, + 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, + 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, + 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, + 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, + }; + auto lower = static_cast(cp); + if (cp < 0x10000) { + return is_printable(lower, singletons0, + sizeof(singletons0) / sizeof(*singletons0), + singletons0_lower, normal0, sizeof(normal0)); + } + if (cp < 0x20000) { + return is_printable(lower, singletons1, + sizeof(singletons1) / sizeof(*singletons1), + singletons1_lower, normal1, sizeof(normal1)); + } + if (0x2a6de <= cp && cp < 0x2a700) return false; + if (0x2b735 <= cp && cp < 0x2b740) return false; + if (0x2b81e <= cp && cp < 0x2b820) return false; + if (0x2cea2 <= cp && cp < 0x2ceb0) return false; + if (0x2ebe1 <= cp && cp < 0x2f800) return false; + if (0x2fa1e <= cp && cp < 0x30000) return false; + if (0x3134b <= cp && cp < 0xe0100) return false; + if (0xe01f0 <= cp && cp < 0x110000) return false; + return cp < 0x110000; +} + +} // namespace detail + +FMT_END_NAMESPACE + +#endif // FMT_FORMAT_INL_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/format.h b/lib/spdlog/include/spdlog/fmt/bundled/format.h new file mode 100644 index 0000000..4a65300 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/format.h @@ -0,0 +1,4395 @@ +/* + Formatting library for C++ + + 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. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES +#endif + +#include "base.h" + +// libc++ supports string_view in pre-c++17. +#if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# define FMT_USE_STRING_VIEW +#endif + +#ifndef FMT_MODULE +# include // malloc, free + +# include // std::signbit +# include // std::byte +# include // uint32_t +# include // std::memcpy +# include // std::numeric_limits +# include // std::bad_alloc +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error + +// Check FMT_CPLUSPLUS to avoid a warning in MSVC. +# if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include // std::bit_cast +# endif + +# if defined(FMT_USE_STRING_VIEW) +# include +# endif + +# if FMT_MSC_VERSION +# include // _BitScanReverse[64], _umul128 +# endif +#endif // FMT_MODULE + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif + +#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L +# define FMT_INLINE_VARIABLE inline +#else +# define FMT_INLINE_VARIABLE +#endif + +// Check if RTTI is disabled. +#ifdef FMT_USE_RTTI +// Use the provided definition. +#elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# define FMT_USE_RTTI 1 +#else +# define FMT_USE_RTTI 0 +#endif + +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) +#else +# define FMT_SO_VISIBILITY(value) +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +// Detect constexpr std::string. +#if !FMT_USE_CONSTEVAL +# define FMT_USE_CONSTEXPR_STRING 0 +#elif defined(__cpp_lib_constexpr_string) && \ + __cpp_lib_constexpr_string >= 201907L +# if FMT_CLANG_VERSION && FMT_GLIBCXX_RELEASE +// clang + libstdc++ are able to work only starting with gcc13.3 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113294 +# if FMT_GLIBCXX_RELEASE < 13 +# define FMT_USE_CONSTEXPR_STRING 0 +# elif FMT_GLIBCXX_RELEASE == 13 && __GLIBCXX__ < 20240521 +# define FMT_USE_CONSTEXPR_STRING 0 +# else +# define FMT_USE_CONSTEXPR_STRING 1 +# endif +# else +# define FMT_USE_CONSTEXPR_STRING 1 +# endif +#else +# define FMT_USE_CONSTEXPR_STRING 0 +#endif +#if FMT_USE_CONSTEXPR_STRING +# define FMT_CONSTEXPR_STRING constexpr +#else +# define FMT_CONSTEXPR_STRING +#endif + +// GCC 4.9 doesn't support qualified names in specializations. +namespace std { +template struct iterator_traits> { + using iterator_category = output_iterator_tag; + using value_type = T; + using difference_type = + decltype(static_cast(nullptr) - static_cast(nullptr)); + using pointer = void; + using reference = void; +}; +} // namespace std + +#ifdef FMT_THROW +// Use the provided definition. +#elif FMT_USE_EXCEPTIONS +# define FMT_THROW(x) throw x +#else +# define FMT_THROW(x) ::fmt::assert_fail(__FILE__, __LINE__, (x).what()) +#endif + +#ifdef __clang_analyzer__ +# define FMT_CLANG_ANALYZER 1 +#else +# define FMT_CLANG_ANALYZER 0 +#endif + +// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of +// integer formatter template instantiations to just one by only using the +// largest integer type. This results in a reduction in binary size but will +// cause a decrease in integer formatting performance. +#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) +# define FMT_REDUCE_INT_INSTANTIATIONS 0 +#endif + +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + +// __builtin_clz is broken in clang with Microsoft codegen: +// https://github.com/fmtlib/fmt/issues/519. +#if !FMT_MSC_VERSION +# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif +# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC but otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# ifdef _WIN64 +# pragma intrinsic(_BitScanReverse64) +# endif +# endif + +inline auto clz(uint32_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; + _BitScanReverse(&r, x); + return 31 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZ(n) detail::clz(n) + +inline auto clzll(uint64_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast(x >> 32))) + return 63 ^ static_cast(r + 32); + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast(x)); +# endif + return 63 ^ static_cast(r); +} +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) +#endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) + +FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { + ignore_unused(condition); +#ifdef FMT_FUZZ + if (condition) throw std::runtime_error("fuzzing limit reached"); +#endif +} + +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#else +template struct std_string_view { + operator basic_string_view() const; +}; +#endif + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { + return {value, sizeof...(C)}; + } +}; +#if FMT_CPLUSPLUS < 201703L +template +constexpr Char string_literal::value[sizeof...(C)]; +#endif + +// Implementation of std::bit_cast for pre-C++20. +template +FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { +#ifdef __cpp_lib_bit_cast + if (is_constant_evaluated()) return std::bit_cast(from); +#endif + auto to = To(); + // The cast suppresses a bogus -Wclass-memaccess on GCC. + std::memcpy(static_cast(&to), &from, sizeof(to)); + return to; +} + +inline auto is_big_endian() -> bool { +#ifdef _WIN32 + return false; +#elif defined(__BIG_ENDIAN__) + return true; +#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) + return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; +#else + struct bytes { + char data[sizeof(int)]; + }; + return bit_cast(1).data[0] == 0; +#endif +} + +class uint128_fallback { + private: + uint64_t lo_, hi_; + + public: + constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} + constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} + + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } + + template ::value)> + constexpr explicit operator T() const { + return static_cast(lo_); + } + + friend constexpr auto operator==(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; + } + friend constexpr auto operator!=(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return !(lhs == rhs); + } + friend constexpr auto operator>(const uint128_fallback& lhs, + const uint128_fallback& rhs) -> bool { + return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; + } + friend constexpr auto operator|(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; + } + friend constexpr auto operator&(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; + } + friend constexpr auto operator~(const uint128_fallback& n) + -> uint128_fallback { + return {~n.hi_, ~n.lo_}; + } + friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { + auto result = uint128_fallback(lhs); + result += rhs; + return result; + } + friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) + -> uint128_fallback { + FMT_ASSERT(lhs.hi_ == 0, ""); + uint64_t hi = (lhs.lo_ >> 32) * rhs; + uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; + uint64_t new_lo = (hi << 32) + lo; + return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; + } + friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) + -> uint128_fallback { + return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; + } + FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { + if (shift == 64) return {0, hi_}; + if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); + return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; + } + FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { + if (shift == 64) return {lo_, 0}; + if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); + return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; + } + FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { + return *this = *this >> shift; + } + FMT_CONSTEXPR void operator+=(uint128_fallback n) { + uint64_t new_lo = lo_ + n.lo_; + uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); + FMT_ASSERT(new_hi >= hi_, ""); + lo_ = new_lo; + hi_ = new_hi; + } + FMT_CONSTEXPR void operator&=(uint128_fallback n) { + lo_ &= n.lo_; + hi_ &= n.hi_; + } + + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { + if (is_constant_evaluated()) { + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); + return *this; + } +#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) + unsigned long long carry; + lo_ = __builtin_addcll(lo_, n, 0, &carry); + hi_ += carry; +#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) + unsigned long long result; + auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); + lo_ = result; + hi_ += carry; +#elif defined(_MSC_VER) && defined(_M_X64) + auto carry = _addcarry_u64(0, lo_, n, &lo_); + _addcarry_u64(carry, hi_, 0, &hi_); +#else + lo_ += n; + hi_ += (lo_ < n ? 1 : 0); +#endif + return *this; + } +}; + +using uint128_t = conditional_t; + +#ifdef UINTPTR_MAX +using uintptr_t = ::uintptr_t; +#else +using uintptr_t = uint128_t; +#endif + +// Returns the largest possible value for type T. Same as +// std::numeric_limits::max() but shorter and not affected by the max macro. +template constexpr auto max_value() -> T { + return (std::numeric_limits::max)(); +} +template constexpr auto num_bits() -> int { + return std::numeric_limits::digits; +} +// std::numeric_limits::digits may return 0 for 128-bit ints. +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } + +// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t +// and 128-bit pointers to uint128_fallback. +template sizeof(From))> +inline auto bit_cast(const From& from) -> To { + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); + struct data_t { + unsigned short value[static_cast(size)]; + } data = bit_cast(from); + auto result = To(); + if (const_check(is_big_endian())) { + for (int i = 0; i < size; ++i) + result = (result << num_bits()) | data.value[i]; + } else { + for (int i = size - 1; i >= 0; --i) + result = (result << num_bits()) | data.value[i]; + } + return result; +} + +template +FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { + int lz = 0; + constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); + for (; (n & msb_mask) == 0; n <<= 1) lz++; + return lz; +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); +#endif + return countl_zero_fallback(n); +} + +FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); +#endif + return countl_zero_fallback(n); +} + +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION + __builtin_assume(condition); +#elif FMT_GCC_VERSION + if (!condition) __builtin_unreachable(); +#endif +} + +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. +template ::value&& + is_contiguous::value)> +#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +FMT_CONSTEXPR20 inline auto +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); + size_t size = c.size(); + c.resize(size + n); + return &c[size]; +} + +template +FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) + -> basic_appender { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + return it; +} + +template +constexpr auto reserve(Iterator& it, size_t) -> Iterator& { + return it; +} + +template +using reserve_iterator = + remove_reference_t(), 0))>; + +template +constexpr auto to_pointer(OutputIt, size_t) -> T* { + return nullptr; +} +template FMT_CONSTEXPR auto to_pointer(T*& ptr, size_t n) -> T* { + T* begin = ptr; + ptr += n; + return begin; +} +template +FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { + buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); + auto size = buf.size(); + if (buf.capacity() < size + n) return nullptr; + buf.try_resize(size + n); + return buf.data() + size; +} + +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { + return it; +} + +template +constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { + return it; +} + +// is spectacularly slow to compile in C++20 so use a simple fill_n +// instead (#1998). +template +FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) + -> OutputIt { + for (Size i = 0; i < count; ++i) *out++ = value; + return out; +} +template +FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { + if (is_constant_evaluated()) return fill_n(out, count, value); + static_assert(sizeof(T) == 1, + "sizeof(T) must be 1 to use char for initialization"); + std::memset(out, value, to_unsigned(count)); + return out + count; +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); +} + +// A public domain branchless UTF-8 decoder by Christopher Wellons: +// https://github.com/skeeto/branchless-utf8 +/* Decode the next character, c, from s, reporting errors in e. + * + * Since this is a branchless decoder, four bytes will be read from the + * buffer regardless of the actual length of the next character. This + * means the buffer _must_ have at least three bytes of zero padding + * following the end of the data stream. + * + * Errors are reported in e, which will be non-zero if the parsed + * character was somehow invalid: invalid byte sequence, non-canonical + * encoding, or a surrogate half. + * + * The function returns a pointer to the next character. When an error + * occurs, this pointer will be a guess that depends on the particular + * error, but it will always advance at least one byte. + */ +FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) + -> const char* { + constexpr int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr int shiftc[] = {0, 18, 12, 6, 0}; + constexpr int shifte[] = {0, 6, 4, 2, 0}; + + int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(*s) >> 3]; + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + const char* next = s + len + !len; + + using uchar = unsigned char; + + // Assume a four-byte character and load four bytes. Unused bits are + // shifted out. + *c = uint32_t(uchar(s[0]) & masks[len]) << 18; + *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; + *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; + *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; + *c >>= shiftc[len]; + + // Accumulate the various error conditions. + *e = (*c < mins[len]) << 6; // non-canonical encoding + *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? + *e |= (*c > 0x10FFFF) << 8; // out of range? + *e |= (uchar(s[1]) & 0xc0) >> 2; + *e |= (uchar(s[2]) & 0xc0) >> 4; + *e |= uchar(s[3]) >> 6; + *e ^= 0x2a; // top two bits of each tail byte correct? + *e >>= shifte[len]; + + return next; +} + +constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); + +// Invokes f(cp, sv) for every code point cp in s with sv being the string view +// corresponding to the code point. cp is invalid_code_point on error. +template +FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { + auto decode = [f](const char* buf_ptr, const char* ptr) { + auto cp = uint32_t(); + auto error = 0; + auto end = utf8_decode(buf_ptr, &cp, &error); + bool result = f(error ? invalid_code_point : cp, + string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); + return result ? (error ? buf_ptr + 1 : end) : nullptr; + }; + + auto p = s.data(); + const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. + if (s.size() >= block_size) { + for (auto end = p + s.size() - block_size + 1; p < end;) { + p = decode(p, p); + if (!p) return; + } + } + auto num_chars_left = to_unsigned(s.data() + s.size() - p); + if (num_chars_left == 0) return; + + // Suppress bogus -Wstringop-overflow. + if (FMT_GCC_VERSION) num_chars_left &= 3; + char buf[2 * block_size - 1] = {}; + copy(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr < buf + num_chars_left); +} + +FMT_CONSTEXPR inline auto display_width_of(uint32_t cp) noexcept -> size_t { + return to_unsigned( + 1 + (cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo init. consonants + cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: + (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || + (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables + (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs + (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms + (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms + (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms + (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms + (cp >= 0x20000 && cp <= 0x2fffd) || // CJK + (cp >= 0x30000 && cp <= 0x3fffd) || + // Miscellaneous Symbols and Pictographs + Emoticons: + (cp >= 0x1f300 && cp <= 0x1f64f) || + // Supplemental Symbols and Pictographs: + (cp >= 0x1f900 && cp <= 0x1f9ff)))); +} + +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION >= 309 && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 +#endif +#if FMT_USE_FLOAT128 +using float128 = __float128; +#else +struct float128 {}; +#endif + +template using is_float128 = std::is_same; + +template struct is_floating_point : std::is_floating_point {}; +template <> struct is_floating_point : std::true_type {}; + +template ::value> +struct is_fast_float : bool_constant::is_iec559 && + sizeof(T) <= sizeof(double)> {}; +template struct is_fast_float : std::false_type {}; + +template +using fast_float_t = conditional_t; + +template +using is_double_double = bool_constant::digits == 106>; + +#ifndef FMT_USE_FULL_CACHE_DRAGONBOX +# define FMT_USE_FULL_CACHE_DRAGONBOX 0 +#endif + +// An allocator that uses malloc/free to allow removing dependency on the C++ +// standard libary runtime. std::decay is used for back_inserter to be found by +// ADL when applied to memory_buffer. +template struct allocator : private std::decay { + using value_type = T; + + auto allocate(size_t n) -> T* { + FMT_ASSERT(n <= max_value() / sizeof(T), ""); + T* p = static_cast(malloc(n * sizeof(T))); + if (!p) FMT_THROW(std::bad_alloc()); + return p; + } + + void deallocate(T* p, size_t) { free(p); } + + constexpr friend auto operator==(allocator, allocator) noexcept -> bool { + return true; // All instances of this allocator are equivalent. + } + constexpr friend auto operator!=(allocator, allocator) noexcept -> bool { + return false; + } +}; + +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); +} +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + +} // namespace detail + +FMT_BEGIN_EXPORT + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. + */ +template > +class basic_memory_buffer : public detail::buffer { + private: + T store_[SIZE]; + + // Don't inherit from Allocator to avoid generating type_info for it. + FMT_NO_UNIQUE_ADDRESS Allocator alloc_; + + // Deallocate memory allocated by the buffer. + FMT_CONSTEXPR20 void deallocate() { + T* data = this->data(); + if (data != store_) alloc_.deallocate(data, this->capacity()); + } + + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { + detail::abort_fuzzing_if(size > 5000); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = max_of(size, max_size); + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); + // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). + detail::assume(buf.size() <= new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); + } + + public: + using value_type = T; + using const_reference = const T&; + + FMT_CONSTEXPR explicit basic_memory_buffer( + const Allocator& alloc = Allocator()) + : detail::buffer(grow), alloc_(alloc) { + this->set(store_, SIZE); + if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); + } + FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } + + private: + template :: + propagate_on_container_move_assignment::value)> + FMT_CONSTEXPR20 auto move_alloc(basic_memory_buffer& other) -> bool { + alloc_ = std::move(other.alloc_); + return true; + } + // If the allocator does not propagate then copy the data from other. + template :: + propagate_on_container_move_assignment::value)> + FMT_CONSTEXPR20 auto move_alloc(basic_memory_buffer& other) -> bool { + T* data = other.data(); + if (alloc_ == other.alloc_ || data == other.store_) return true; + size_t size = other.size(); + // Perform copy operation, allocators are different. + this->resize(size); + detail::copy(data, data + size, this->data()); + return false; + } + + // Move data from other to this buffer. + FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { + T* data = other.data(); + size_t size = other.size(), capacity = other.capacity(); + if (!move_alloc(other)) return; + if (data == other.store_) { + this->set(store_, capacity); + detail::copy(other.store_, other.store_ + size, store_); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + other.clear(); + } + this->resize(size); + } + + public: + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { + move(other); + } + + /// Moves the content of the other `basic_memory_buffer` object to this one. + auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { + FMT_ASSERT(this != &other, ""); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + auto get_allocator() const -> Allocator { return alloc_; } + + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. + FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } + + /// Increases the buffer capacity to `new_capacity`. + void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } + + using detail::buffer::append; + template + FMT_CONSTEXPR20 void append(const ContiguousRange& range) { + append(range.data(), range.data() + range.size()); + } +}; + +using memory_buffer = basic_memory_buffer; + +template +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) + -> std::string { + auto size = buf.size(); + detail::assume(size < std::string().max_size()); + return {buf.data(), size}; +} + +// A writer to a buffered stream. It doesn't own the underlying stream. +class writer { + private: + detail::buffer* buf_; + + // We cannot create a file buffer in advance because any write to a FILE may + // invalidate it. + FILE* file_; + + public: + inline writer(FILE* f) : buf_(nullptr), file_(f) {} + inline writer(detail::buffer& buf) : buf_(&buf) {} + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + if (buf_) + fmt::format_to(appender(*buf_), fmt, std::forward(args)...); + else + fmt::print(file_, fmt, std::forward(args)...); + } +}; + +class string_buffer { + private: + std::string str_; + detail::container_buffer buf_; + + public: + inline string_buffer() : buf_(str_) {} + + inline operator writer() { return buf_; } + inline auto str() -> std::string& { return str_; } +}; + +template +struct is_contiguous> : std::true_type { +}; + +// Suppress a misleading warning in older versions of clang. +FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") + +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { + public: + using std::runtime_error::runtime_error; +}; + +class loc_value; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(FILE*, string_view); +} // namespace detail + +namespace detail { +template struct fixed_string { + FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + detail::copy(static_cast(s), s + N, + data); + } + Char data[N] = {}; +}; + +// Converts a compile-time string to basic_string_view. +FMT_EXPORT template +constexpr auto compile_string_to_view(const Char (&s)[N]) + -> basic_string_view { + // Remove trailing NUL character if needed. Won't be present if this is used + // with a raw character array (i.e. not defined as a string). + return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; +} +FMT_EXPORT template +constexpr auto compile_string_to_view(basic_string_view s) + -> basic_string_view { + return s; +} + +// Returns true if value is negative, false otherwise. +// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. +template ::value)> +constexpr auto is_negative(T value) -> bool { + return value < 0; +} +template ::value)> +constexpr auto is_negative(T) -> bool { + return false; +} + +// Smallest of uint32_t, uint64_t, uint128_t that is large enough to +// represent all values of an integral type T. +template +using uint32_or_64_or_128_t = + conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, + uint32_t, + conditional_t() <= 64, uint64_t, uint128_t>>; +template +using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 + +// Converts value in the range [0, 100) to a string. +// GCC generates slightly better code when value is pointer-size. +inline auto digits2(size_t value) -> const char* { + // Align data since unaligned access may be slower when crossing a + // hardware-specific boundary. + alignas(2) static const char data[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + return &data[value * 2]; +} + +template constexpr auto getsign(sign s) -> Char { + return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> + (static_cast(s) * 8)); +} + +template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { + int count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#if FMT_USE_INT128 +FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { + return count_digits_fallback(n); +} +#endif + +#ifdef FMT_BUILTIN_CLZLL +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +inline auto do_count_digits(uint64_t n) -> int { + // This has comparable performance to the version by Kendall Willets + // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) + // but uses smaller tables. + // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). + static constexpr uint8_t bsr2log10[] = { + 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; + auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; + static constexpr uint64_t zero_or_powers_of_10[] = { + 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), + 10000000000000000000ULL}; + return t - (n < zero_or_powers_of_10[t]); +} +#endif + +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { +#ifdef FMT_BUILTIN_CLZLL + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +// Counts the number of digits in n. BITS = log2(radix). +template +FMT_CONSTEXPR auto count_digits(UInt n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && num_bits() == 32) + return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; +#endif + // Lambda avoids unreachable code warnings from NVHPC. + return [](UInt m) { + int num_digits = 0; + do { + ++num_digits; + } while ((m >>= BITS) != 0); + return num_digits; + }(n); +} + +#ifdef FMT_BUILTIN_CLZ +// It is a separate function rather than a part of count_digits to workaround +// the lack of static constexpr in constexpr functions. +FMT_INLINE auto do_count_digits(uint32_t n) -> int { +// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. +// This increments the upper 32 bits (log10(T) - 1) when >= T is added. +# define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T) + static constexpr uint64_t table[] = { + FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 + FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 + FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 + FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 + FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k + FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k + FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k + FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M + FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M + FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M + FMT_INC(1000000000), FMT_INC(1000000000) // 4B + }; + auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; + return static_cast((n + inc) >> 32); +} +#endif + +// Optional version of count_digits for better performance on 32-bit platforms. +FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { +#ifdef FMT_BUILTIN_CLZ + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); +#endif + return count_digits_fallback(n); +} + +template constexpr auto digits10() noexcept -> int { + return std::numeric_limits::digits10; +} +template <> constexpr auto digits10() noexcept -> int { return 38; } +template <> constexpr auto digits10() noexcept -> int { return 38; } + +template struct thousands_sep_result { + std::string grouping; + Char thousands_sep; +}; + +template +FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; +template +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + auto result = thousands_sep_impl(loc); + return {result.grouping, Char(result.thousands_sep)}; +} +template <> +inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { + return thousands_sep_impl(loc); +} + +template +FMT_API auto decimal_point_impl(locale_ref loc) -> Char; +template inline auto decimal_point(locale_ref loc) -> Char { + return Char(decimal_point_impl(loc)); +} +template <> inline auto decimal_point(locale_ref loc) -> wchar_t { + return decimal_point_impl(loc); +} + +#ifndef FMT_HEADER_ONLY +FMT_BEGIN_EXPORT +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto decimal_point_impl(locale_ref) -> char; +extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; +FMT_END_EXPORT +#endif // FMT_HEADER_ONLY + +// Compares two characters for equality. +template auto equal2(const Char* lhs, const char* rhs) -> bool { + return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); +} +inline auto equal2(const char* lhs, const char* rhs) -> bool { + return memcmp(lhs, rhs, 2) == 0; +} + +// Writes a two-digit value to out. +template +FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { + if (!is_constant_evaluated() && std::is_same::value && + !FMT_OPTIMIZE_SIZE) { + memcpy(out, digits2(value), 2); + return; + } + *out++ = static_cast('0' + value / 10); + *out = static_cast('0' + value % 10); +} + +// Formats a decimal unsigned integer value writing to out pointing to a buffer +// of specified size. The caller must ensure that the buffer is large enough. +template +FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) + -> Char* { + FMT_ASSERT(size >= count_digits(value), "invalid digit count"); + unsigned n = to_unsigned(size); + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + n -= 2; + write2digits(out + n, static_cast(value % 100)); + value /= 100; + } + if (value >= 10) { + n -= 2; + write2digits(out + n, static_cast(value)); + } else { + out[--n] = static_cast('0' + value); + } + return out + n; +} + +template +FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, + int num_digits) -> Char* { + do_format_decimal(out, value, num_digits); + return out + num_digits; +} + +template >::value)> +FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + do_format_decimal(ptr, value, num_digits); + return out; + } + // Buffer is large enough to hold all digits (digits10 + 1). + char buffer[digits10() + 1]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + do_format_decimal(buffer, value, num_digits); + return copy_noinline(buffer, buffer + num_digits, out); +} + +template +FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, + int size, bool upper = false) -> Char* { + out += size; + do { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1u << base_bits) - 1)); + *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) + : digits[digit]); + } while ((value >>= base_bits) != 0); + return out; +} + +// Formats an unsigned integer in the power of two base (binary, octal, hex). +template +FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, + int num_digits, bool upper = false) -> Char* { + do_format_base2e(base_bits, out, value, num_digits, upper); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, + int num_digits, bool upper = false) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_base2e(base_bits, ptr, value, num_digits, upper); + return out; + } + // Make buffer large enough for any base. + char buffer[num_bits()]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + format_base2e(base_bits, buffer, value, num_digits, upper); + return detail::copy_noinline(buffer, buffer + num_digits, out); +} + +// A converter from UTF-8 to UTF-16. +class utf8_to_utf16 { + private: + basic_memory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + inline operator basic_string_view() const { + return {&buffer_[0], size()}; + } + inline auto size() const -> size_t { return buffer_.size() - 1; } + inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } + inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } +}; + +enum class to_utf8_error_policy { abort, replace }; + +// A converter from UTF-16/UTF-32 (host endian) to UTF-8. +template class to_utf8 { + private: + Buffer buffer_; + + public: + to_utf8() {} + explicit to_utf8(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, + "expected utf16 or utf32"); + if (!convert(s, policy)) { + FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" + : "invalid utf32")); + } + } + operator string_view() const { return string_view(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a bool instead of throwing exception on + // conversion error. This method may still throw in case of memory allocation + // error. + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + if (!convert(buffer_, s, policy)) return false; + buffer_.push_back(0); + return true; + } + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { + for (auto p = s.begin(); p != s.end(); ++p) { + uint32_t c = static_cast(*p); + if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { + // Handle a surrogate pair. + ++p; + if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { + if (policy == to_utf8_error_policy::abort) return false; + buf.append(string_view("\xEF\xBF\xBD")); + --p; + continue; + } + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + return false; + } + } + return true; + } +}; + +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +FMT_INLINE auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto hi = uint64_t(); + auto lo = _umul128(x, y, &hi); + return {hi, lo}; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + +namespace dragonbox { +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline auto floor_log10_pow2(int e) noexcept -> int { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + +inline auto floor_log2_pow10(int e) noexcept -> int { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} + +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; + +// Type-specific information that Dragonbox uses. +template struct float_info; + +template <> struct float_info { + using carrier_uint = uint32_t; + static const int exponent_bits = 8; + static const int kappa = 1; + static const int big_divisor = 100; + static const int small_divisor = 10; + static const int min_k = -31; + static const int max_k = 46; + static const int shorter_interval_tie_lower_threshold = -35; + static const int shorter_interval_tie_upper_threshold = -35; +}; + +template <> struct float_info { + using carrier_uint = uint64_t; + static const int exponent_bits = 11; + static const int kappa = 2; + static const int big_divisor = 1000; + static const int small_divisor = 100; + static const int min_k = -292; + static const int max_k = 341; + static const int shorter_interval_tie_lower_threshold = -77; + static const int shorter_interval_tie_upper_threshold = -77; +}; + +// An 80- or 128-bit floating point number. +template +struct float_info::digits == 64 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + static const int exponent_bits = 15; +}; + +// A double-double floating point number. +template +struct float_info::value>> { + using carrier_uint = detail::uint128_t; +}; + +template struct decimal_fp { + using significand_type = typename float_info::carrier_uint; + significand_type significand; + int exponent; +}; + +template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; +} // namespace dragonbox + +// Returns true iff Float has the implicit bit which is not stored. +template constexpr auto has_implicit_bit() -> bool { + // An 80-bit FP number has a 64-bit significand an no implicit bit. + return std::numeric_limits::digits != 64; +} + +// Returns the number of significand bits stored in Float. The implicit bit is +// not counted since it is not stored. +template constexpr auto num_significand_bits() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 112 + : (std::numeric_limits::digits - + (has_implicit_bit() ? 1 : 0)); +} + +template +constexpr auto exponent_mask() -> + typename dragonbox::float_info::carrier_uint { + using float_uint = typename dragonbox::float_info::carrier_uint; + return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + << num_significand_bits(); +} +template constexpr auto exponent_bias() -> int { + // std::numeric_limits may not support __float128. + return is_float128() ? 16383 + : std::numeric_limits::max_exponent - 1; +} + +FMT_CONSTEXPR inline auto compute_exp_size(int exp) -> int { + auto prefix_size = 2; // sign + 'e' + auto abs_exp = exp >= 0 ? exp : -exp; + if (abs_exp < 100) return prefix_size + 2; + return prefix_size + (abs_exp >= 1000 ? 4 : 3); +} + +// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. +template +FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { + FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); + if (exp < 0) { + *out++ = static_cast('-'); + exp = -exp; + } else { + *out++ = static_cast('+'); + } + auto uexp = static_cast(exp); + if (is_constant_evaluated()) { + if (uexp < 10) *out++ = '0'; + return format_decimal(out, uexp, count_digits(uexp)); + } + if (uexp >= 100u) { + const char* top = digits2(uexp / 100); + if (uexp >= 1000u) *out++ = static_cast(top[0]); + *out++ = static_cast(top[1]); + uexp %= 100; + } + const char* d = digits2(uexp); + *out++ = static_cast(d[0]); + *out++ = static_cast(d[1]); + return out; +} + +// A floating-point number f * pow(2, e) where F is an unsigned type. +template struct basic_fp { + F f; + int e; + + static constexpr int num_significand_bits = + static_cast(sizeof(F) * num_bits()); + + constexpr basic_fp() : f(0), e(0) {} + constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} + + // Constructs fp from an IEEE754 floating-point number. + template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } + + // Assigns n to this and return true iff predecessor is closer than successor. + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename dragonbox::float_info::carrier_uint; + const auto num_float_significand_bits = + detail::num_significand_bits(); + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + const auto significand_mask = implicit_bit - 1; + auto u = bit_cast(n); + f = static_cast(u & significand_mask); + auto biased_e = static_cast((u & exponent_mask()) >> + num_float_significand_bits); + // The predecessor is closer if n is a normalized power of 2 (f == 0) + // other than the smallest normalized number (biased_e > 1). + auto is_predecessor_closer = f == 0 && biased_e > 1; + if (biased_e == 0) + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + else if (has_implicit_bit()) + f += static_cast(implicit_bit); + e = biased_e - exponent_bias() - num_float_significand_bits; + if (!has_implicit_bit()) ++e; + return is_predecessor_closer; + } + + template ::value)> + FMT_CONSTEXPR auto assign(Float n) -> bool { + static_assert(std::numeric_limits::is_iec559, "unsupported FP"); + return assign(static_cast(n)); + } +}; + +using fp = basic_fp; + +// Normalizes the value converted from double and multiplied by (1 << SHIFT). +template +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { + // Handle subnormals. + const auto implicit_bit = F(1) << num_significand_bits(); + const auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((value.f & shifted_implicit_bit) == 0) { + value.f <<= 1; + --value.e; + } + // Subtract 1 to account for hidden bit. + const auto offset = basic_fp::num_significand_bits - + num_significand_bits() - SHIFT - 1; + value.f <<= offset; + value.e -= offset; + return value; +} + +// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { +#if FMT_USE_INT128 + auto product = static_cast<__uint128_t>(lhs) * rhs; + auto f = static_cast(product >> 64); + return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; +#else + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = lhs >> 32, b = lhs & mask; + uint64_t c = rhs >> 32, d = rhs & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); +#endif +} + +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { + return {multiply(x.f, y.f), x.e + y.e + 64}; +} + +template () == num_bits()> +using convert_float_result = + conditional_t::value || doublish, double, T>; + +template +constexpr auto convert_float(T value) -> convert_float_result { + return static_cast>(value); +} + +template +auto select(T true_value, F) -> T { + return true_value; +} +template +auto select(T, F false_value) -> F { + return false_value; +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto fill(OutputIt it, size_t n, + const basic_specs& specs) -> OutputIt { + auto fill_size = specs.fill_size(); + if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); + if (const Char* data = specs.fill()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } + return it; +} + +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, + size_t size, size_t width, F&& f) -> OutputIt { + static_assert(default_align == align::left || default_align == align::right, + ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + // Shifts are encoded as string literals because static constexpr is not + // supported in constexpr functions. + auto* shifts = + default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; + size_t left_padding = padding >> shifts[static_cast(specs.align())]; + size_t right_padding = padding - left_padding; + auto it = reserve(out, size + padding * specs.fill_size()); + if (left_padding != 0) it = fill(it, left_padding, specs); + it = f(it); + if (right_padding != 0) it = fill(it, right_padding, specs); + return base_iterator(out, it); +} + +template +constexpr auto write_padded(OutputIt out, const format_specs& specs, + size_t size, F&& f) -> OutputIt { + return write_padded(out, specs, size, size, f); +} + +template +FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, + const format_specs& specs = {}) -> OutputIt { + return write_padded( + out, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy(data, data + bytes.size(), it); + }); +} + +template +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) + -> OutputIt { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_base2e(4, it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +// Returns true iff the code point cp is printable. +FMT_API auto is_printable(uint32_t cp) -> bool; + +inline auto needs_escape(uint32_t cp) -> bool { + if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; + if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; + return !is_printable(cp); +} + +template struct find_escape_result { + const Char* begin; + const Char* end; + uint32_t cp; +}; + +template +auto find_escape(const Char* begin, const Char* end) + -> find_escape_result { + for (; begin != end; ++begin) { + uint32_t cp = static_cast>(*begin); + if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; + if (needs_escape(cp)) return {begin, begin + 1, cp}; + } + return {begin, nullptr, 0}; +} + +inline auto find_escape(const char* begin, const char* end) + -> find_escape_result { + if (const_check(!use_utf8)) return find_escape(begin, end); + auto result = find_escape_result{end, nullptr, 0}; + for_each_codepoint(string_view(begin, to_unsigned(end - begin)), + [&](uint32_t cp, string_view sv) { + if (needs_escape(cp)) { + result = {sv.begin(), sv.end(), cp}; + return false; + } + return true; + }); + return result; +} + +template +auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { + *out++ = static_cast('\\'); + *out++ = static_cast(prefix); + Char buf[width]; + fill_n(buf, width, static_cast('0')); + format_base2e(4, buf, cp, width); + return copy(buf, buf + width, out); +} + +template +auto write_escaped_cp(OutputIt out, const find_escape_result& escape) + -> OutputIt { + auto c = static_cast(escape.cp); + switch (escape.cp) { + case '\n': + *out++ = static_cast('\\'); + c = static_cast('n'); + break; + case '\r': + *out++ = static_cast('\\'); + c = static_cast('r'); + break; + case '\t': + *out++ = static_cast('\\'); + c = static_cast('t'); + break; + case '"': FMT_FALLTHROUGH; + case '\'': FMT_FALLTHROUGH; + case '\\': *out++ = static_cast('\\'); break; + default: + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) + return write_codepoint<4, Char>(out, 'u', escape.cp); + if (escape.cp < 0x110000) + return write_codepoint<8, Char>(out, 'U', escape.cp); + for (Char escape_char : basic_string_view( + escape.begin, to_unsigned(escape.end - escape.begin))) { + out = write_codepoint<2, Char>(out, 'x', + static_cast(escape_char) & 0xFF); + } + return out; + } + *out++ = c; + return out; +} + +template +auto write_escaped_string(OutputIt out, basic_string_view str) + -> OutputIt { + *out++ = static_cast('"'); + auto begin = str.begin(), end = str.end(); + do { + auto escape = find_escape(begin, end); + out = copy(begin, escape.begin, out); + begin = escape.end; + if (!begin) break; + out = write_escaped_cp(out, escape); + } while (begin != end); + *out++ = static_cast('"'); + return out; +} + +template +auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; + *out++ = static_cast('\''); + if ((needs_escape(static_cast(v)) && v != static_cast('"')) || + v == static_cast('\'')) { + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); + } else { + *out++ = v; + } + *out++ = static_cast('\''); + return out; +} + +template +FMT_CONSTEXPR auto write_char(OutputIt out, Char value, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type() == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { + if (is_debug) return write_escaped_char(it, value); + *it++ = value; + return it; + }); +} + +template class digit_grouping { + private: + std::string grouping_; + std::basic_string thousands_sep_; + + struct next_state { + std::string::const_iterator group; + int pos; + }; + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } + + // Returns the next digit group separator position. + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + explicit digit_grouping(locale_ref loc, bool localized = true) { + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + } + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + + auto has_separator() const -> bool { return !thousands_sep_.empty(); } + + auto count_separators(int num_digits) const -> int { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and writes the output to out. + template + auto apply(Out out, basic_string_view digits) const -> Out { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +// Writes a decimal integer with digit grouping. +template +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_base2e(4, appender(buffer), value, num_digits, specs.upper()); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt() && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_base2e(3, appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_base2e(1, appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); + }); +} + +#if FMT_USE_LOCALE +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +auto write_loc(basic_appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool; +#endif +template +inline auto write_loc(OutputIt, const loc_value&, const format_specs&, + locale_ref) -> bool { + return false; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; + prefix = prefixes[static_cast(s)]; + } + return {abs_value, prefix}; +} + +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign()); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +// Size and padding computation separate from write_int to avoid template bloat. +struct size_padding { + unsigned size; + unsigned padding; + + FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, + const format_specs& specs) + : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { + if (specs.align() == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = (prefix >> 24) + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + } + } +}; + +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const format_specs& specs) -> OutputIt { + static_assert(std::is_same>::value, ""); + + constexpr size_t buffer_size = num_bits(); + char buffer[buffer_size]; + if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); + const char* begin = nullptr; + const char* end = buffer + buffer_size; + + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + begin = do_format_decimal(buffer, abs_value, buffer_size); + break; + case presentation_type::hex: + begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + break; + case presentation_type::oct: { + begin = do_format_base2e(3, buffer, abs_value, buffer_size); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + auto num_digits = end - begin; + if (specs.alt() && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + break; + } + case presentation_type::bin: + begin = do_format_base2e(1, buffer, abs_value, buffer_size); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + break; + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); + } + + // Write an integer in the format + // + // prefix contains chars in three lower bytes and the size in the fourth byte. + int num_digits = static_cast(end - begin); + // Slightly faster check for specs.width == 0 && specs.precision == -1. + if ((specs.width | (specs.precision + 1)) == 0) { + auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return base_iterator(out, copy(begin, end, it)); + } + auto sp = size_padding(num_digits, prefix, specs); + unsigned padding = sp.padding; + return write_padded( + out, specs, sp.size, [=](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + it = detail::fill_n(it, padding, static_cast('0')); + return copy(begin, end, it); + }); +} + +template +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs) + -> OutputIt { + return write_int(out, arg, specs); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign()), + specs); +} + +// An inlined version of write used in format string compilation. +template ::value && + !std::is_same::value && + !std::is_same::value && + !std::is_same>::value)> +FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign()), specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, + locale_ref loc = {}) -> OutputIt { + // char is formatted as unsigned char for consistency across platforms. + using unsigned_type = + conditional_t::value, unsigned char, unsigned>; + return check_char_specs(specs) + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type() == presentation_type::debug; + if (specs.precision < 0 && specs.width == 0) { + auto&& it = reserve(out, s.size()); + return is_debug ? write_escaped_string(it, s) : copy(s, it); + } + + size_t display_width_limit = + specs.precision < 0 ? SIZE_MAX : to_unsigned(specs.precision); + size_t display_width = + !is_debug || specs.precision == 0 ? 0 : 1; // Account for opening '"'. + size_t size = !is_debug || specs.precision == 0 ? 0 : 1; + for_each_codepoint(s, [&](uint32_t cp, string_view sv) { + if (is_debug && needs_escape(cp)) { + counting_buffer buf; + write_escaped_cp(basic_appender(buf), + find_escape_result{sv.begin(), sv.end(), cp}); + // We're reinterpreting bytes as display width. That's okay + // because write_escaped_cp() only writes ASCII characters. + size_t cp_width = buf.count(); + if (display_width + cp_width <= display_width_limit) { + display_width += cp_width; + size += cp_width; + // If this is the end of the string, account for closing '"'. + if (display_width < display_width_limit && sv.end() == s.end()) { + ++display_width; + ++size; + } + return true; + } + + size += display_width_limit - display_width; + display_width = display_width_limit; + return false; + } + + size_t cp_width = display_width_of(cp); + if (cp_width + display_width <= display_width_limit) { + display_width += cp_width; + size += sv.size(); + // If this is the end of the string, account for closing '"'. + if (is_debug && display_width < display_width_limit && + sv.end() == s.end()) { + ++display_width; + ++size; + } + return true; + } + + return false; + }); + + struct bounded_output_iterator { + reserve_iterator underlying_iterator; + size_t bound; + + FMT_CONSTEXPR auto operator*() -> bounded_output_iterator& { return *this; } + FMT_CONSTEXPR auto operator++() -> bounded_output_iterator& { + return *this; + } + FMT_CONSTEXPR auto operator++(int) -> bounded_output_iterator& { + return *this; + } + FMT_CONSTEXPR auto operator=(char c) -> bounded_output_iterator& { + if (bound > 0) { + *underlying_iterator++ = c; + --bound; + } + return *this; + } + }; + + return write_padded( + out, specs, size, display_width, [=](reserve_iterator it) { + return is_debug + ? write_escaped_string(bounded_output_iterator{it, size}, s) + .underlying_iterator + : copy(s.data(), s.data() + size, it); + }); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs) -> OutputIt { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = to_unsigned(specs.precision); + + bool is_debug = specs.type() == presentation_type::debug; + if (is_debug) { + auto buf = counting_buffer(); + write_escaped_string(basic_appender(buf), s); + size = buf.count(); + } + + return write_padded( + out, specs, size, [=](reserve_iterator it) { + return is_debug ? write_escaped_string(it, s) + : copy(data, data + size, it); + }); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type() == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); +} + +template ::value && + !std::is_same::value && + !std::is_same::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + auto abs_value = static_cast>(value); + bool negative = is_negative(value); + // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. + if (negative) abs_value = ~abs_value + 1; + int num_digits = count_digits(abs_value); + auto size = (negative ? 1 : 0) + static_cast(num_digits); + if (auto ptr = to_pointer(out, size)) { + if (negative) *ptr++ = static_cast('-'); + format_decimal(ptr, abs_value, num_digits); + return out; + } + if (negative) *out++ = static_cast('-'); + return format_decimal(out, abs_value, num_digits); +} + +template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + format_specs& specs) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto alignment = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': alignment = align::left; break; + case '>': alignment = align::right; break; + case '^': alignment = align::center; break; + } + if (alignment != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '}') return begin; + if (c == '{') { + report_error("invalid fill character '{'"); + return begin; + } + specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else { + ++begin; + } + break; + } else if (p == begin) { + break; + } + p = begin; + } + specs.set_align(alignment); + return begin; +} + +template +FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, + format_specs specs, sign s) -> OutputIt { + auto str = + isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); + constexpr size_t str_size = 3; + auto size = str_size + (s != sign::none ? 1 : 0); + // Replace '0'-padding with space for non-finite values. + const bool is_zero_fill = + specs.fill_size() == 1 && specs.fill_unit() == '0'; + if (is_zero_fill) specs.set_fill(' '); + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (s != sign::none) + *it++ = detail::getsign(s); + return copy(str, str + str_size, it); + }); +} + +// A decimal floating-point number significand * pow(10, exp). +struct big_decimal_fp { + const char* significand; + int significand_size; + int exponent; +}; + +constexpr auto get_significand_size(const big_decimal_fp& f) -> int { + return f.significand_size; +} +template +inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { + return count_digits(f.significand); +} + +template +constexpr auto write_significand(OutputIt out, const char* significand, + int significand_size) -> OutputIt { + return copy(significand, significand + significand_size, out); +} +template +inline auto write_significand(OutputIt out, UInt significand, + int significand_size) -> OutputIt { + return format_decimal(out, significand, significand_size); +} +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int exponent, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + out = write_significand(out, significand, significand_size); + return detail::fill_n(out, exponent, static_cast('0')); + } + auto buffer = memory_buffer(); + write_significand(appender(buffer), significand, significand_size); + detail::fill_n(appender(buffer), exponent, '0'); + return grouping.apply(out, string_view(buffer.data(), buffer.size())); +} + +template ::value)> +inline auto write_significand(Char* out, UInt significand, int significand_size, + int integral_size, Char decimal_point) -> Char* { + if (!decimal_point) return format_decimal(out, significand, significand_size); + out += significand_size + 1; + Char* end = out; + int floating_size = significand_size - integral_size; + for (int i = floating_size / 2; i > 0; --i) { + out -= 2; + write2digits(out, static_cast(significand % 100)); + significand /= 100; + } + if (floating_size % 2 != 0) { + *--out = static_cast('0' + significand % 10); + significand /= 10; + } + *--out = decimal_point; + format_decimal(out - integral_size, significand, integral_size); + return end; +} + +template >::value)> +inline auto write_significand(OutputIt out, UInt significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. + Char buffer[digits10() + 2]; + auto end = write_significand(buffer, significand, significand_size, + integral_size, decimal_point); + return detail::copy_noinline(buffer, end, out); +} + +template +FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, + int significand_size, int integral_size, + Char decimal_point) -> OutputIt { + out = detail::copy_noinline(significand, significand + integral_size, + out); + if (!decimal_point) return out; + *out++ = decimal_point; + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); +} + +template +FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, + int significand_size, int integral_size, + Char decimal_point, + const Grouping& grouping) -> OutputIt { + if (!grouping.has_separator()) { + return write_significand(out, significand, significand_size, integral_size, + decimal_point); + } + auto buffer = basic_memory_buffer(); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); + grouping.apply( + out, basic_string_view(buffer.data(), to_unsigned(integral_size))); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); +} + +// Numbers with exponents greater or equal to the returned value will use +// the exponential notation. +template FMT_CONSTEVAL auto exp_upper() -> int { + return std::numeric_limits::digits10 != 0 + ? min_of(16, std::numeric_limits::digits10 + 1) + : 16; +} + +// Use the fixed notation if the exponent is in [-4, exp_upper), +// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. +constexpr auto use_fixed(int exp, int exp_upper) -> bool { + return exp >= -4 && exp < exp_upper; +} + +template class fallback_digit_grouping { + public: + constexpr fallback_digit_grouping(locale_ref, bool) {} + + constexpr auto has_separator() const -> bool { return false; } + + constexpr auto count_separators(int) const -> int { return 0; } + + template + constexpr auto apply(Out out, basic_string_view) const -> Out { + return out; + } +}; + +template +FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f, + int significand_size, Char decimal_point, + const format_specs& specs, sign s, + locale_ref loc = {}) -> OutputIt { + using iterator = reserve_iterator; + + int exp = f.exponent + significand_size; + long long size = significand_size + (s != sign::none ? 1 : 0); + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += f.exponent; + int num_zeros = specs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (specs.alt()) { + ++size; + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) + num_zeros = 0; + if (num_zeros > 0) size += num_zeros; + } + auto grouping = Grouping(loc, specs.localized()); + size += grouping.count_separators(exp); + return write_padded( + out, specs, static_cast(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, f.significand, significand_size, + f.exponent, grouping); + if (!specs.alt()) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; + }); + } + if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; + size += 1 + max_of(num_zeros, 0); + auto grouping = Grouping(loc, specs.localized()); + size += grouping.count_separators(exp); + return write_padded( + out, specs, to_unsigned(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, f.significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && specs.precision >= 0 && + specs.precision < num_zeros) { + num_zeros = specs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); + size += 1 + (pointy ? 1 : 0) + num_zeros; + return write_padded( + out, specs, to_unsigned(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + *it++ = Char('0'); + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, Char('0')); + return write_significand(it, f.significand, significand_size); + }); +} + +template +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + int exp_upper, locale_ref loc) -> OutputIt { + Char point = specs.localized() ? detail::decimal_point(loc) : Char('.'); + int significand_size = get_significand_size(f); + int exp = f.exponent + significand_size - 1; + if (specs.type() == presentation_type::fixed || + (specs.type() != presentation_type::exp && + use_fixed(exp, specs.precision > 0 ? specs.precision : exp_upper))) { + return write_fixed(out, f, significand_size, point, specs, + s, loc); + } + + // Write value in the exponential format. + int num_zeros = 0; + long long size = significand_size + (s != sign::none ? 1 : 0); + if (specs.alt()) { + num_zeros = max_of(specs.precision - significand_size, 0); + size += num_zeros; + } else if (significand_size == 1) { + point = Char(); + } + size += (point ? 1 : 0) + compute_exp_size(exp); + char exp_char = specs.upper() ? 'E' : 'e'; + auto write = [=](reserve_iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, f.significand, significand_size, 1, point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, Char('0')); + *it++ = Char(exp_char); + return write_exponent(exp, it); + }; + auto usize = to_unsigned(size); + return specs.width > 0 + ? write_padded(out, specs, usize, write) + : base_iterator(out, write(reserve(out, usize))); +} + +template +FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + int exp_upper, locale_ref loc) -> OutputIt { + if (is_constant_evaluated()) { + return do_write_float>(out, f, specs, s, + exp_upper, loc); + } else { + return do_write_float>(out, f, specs, s, + exp_upper, loc); + } +} + +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. +} + +template +struct has_isfinite : std::false_type {}; + +template +struct has_isfinite> + : std::true_type {}; + +template ::value&& has_isfinite::value)> +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { + constexpr T inf = T(std::numeric_limits::infinity()); + if (is_constant_evaluated()) + return !detail::isnan(value) && value < inf && value > -inf; + return std::isfinite(value); +} +template ::value)> +FMT_CONSTEXPR auto isfinite(T value) -> bool { + T inf = T(std::numeric_limits::infinity()); + // std::isfinite doesn't support __float128. + return !detail::isnan(value) && value < inf && value > -inf; +} + +template ::value)> +FMT_INLINE FMT_CONSTEXPR auto signbit(T value) -> bool { + if (is_constant_evaluated()) { +#ifdef __cpp_if_constexpr + if constexpr (std::numeric_limits::is_iec559) { + auto bits = detail::bit_cast(static_cast(value)); + return (bits >> (num_bits() - 1)) != 0; + } +#endif + } + return std::signbit(static_cast(value)); +} + +inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { + // Adjust fixed precision by exponent because it is relative to decimal + // point. + if (exp10 > 0 && precision > max_value() - exp10) + FMT_THROW(format_error("number is too big")); + precision += exp10; +} + +class bigint { + private: + // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. + using bigit = uint32_t; // A big digit. + using double_bigit = uint64_t; + enum { bigit_bits = num_bits() }; + enum { bigits_capacity = 32 }; + basic_memory_buffer bigits_; + int exp_; + + friend struct formatter; + + FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { + return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; + } + + FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = double_bigit(bigits_[index]) - other - borrow; + bigits_[index] = static_cast(result); + borrow = static_cast(result >> (bigit_bits * 2 - 1)); + } + + FMT_CONSTEXPR void remove_leading_zeros() { + int num_bigits = static_cast(bigits_.size()) - 1; + while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; + bigits_.resize(to_unsigned(num_bigits + 1)); + } + + // Computes *this -= other assuming aligned bigints and *this >= other. + FMT_CONSTEXPR void subtract_aligned(const bigint& other) { + FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); + FMT_ASSERT(compare(*this, other) >= 0, ""); + bigit borrow = 0; + int i = other.exp_ - exp_; + for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) + subtract_bigits(i, other.bigits_[j], borrow); + if (borrow != 0) subtract_bigits(i, 0, borrow); + FMT_ASSERT(borrow == 0, ""); + remove_leading_zeros(); + } + + FMT_CONSTEXPR void multiply(uint32_t value) { + bigit carry = 0; + const double_bigit wide_value = value; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + double_bigit result = bigits_[i] * wide_value + carry; + bigits_[i] = static_cast(result); + carry = static_cast(result >> bigit_bits); + } + if (carry != 0) bigits_.push_back(carry); + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void multiply(UInt value) { + using half_uint = + conditional_t::value, uint64_t, uint32_t>; + const int shift = num_bits() - bigit_bits; + const UInt lower = static_cast(value); + const UInt upper = value >> num_bits(); + UInt carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + UInt result = lower * bigits_[i] + static_cast(carry); + carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + + (carry >> bigit_bits); + bigits_[i] = static_cast(result); + } + while (carry != 0) { + bigits_.push_back(static_cast(carry)); + carry >>= bigit_bits; + } + } + + template ::value || + std::is_same::value)> + FMT_CONSTEXPR void assign(UInt n) { + size_t num_bigits = 0; + do { + bigits_[num_bigits++] = static_cast(n); + n >>= bigit_bits; + } while (n != 0); + bigits_.resize(num_bigits); + exp_ = 0; + } + + public: + FMT_CONSTEXPR bigint() : exp_(0) {} + explicit bigint(uint64_t n) { assign(n); } + + bigint(const bigint&) = delete; + void operator=(const bigint&) = delete; + + FMT_CONSTEXPR void assign(const bigint& other) { + auto size = other.bigits_.size(); + bigits_.resize(size); + auto data = other.bigits_.data(); + copy(data, data + size, bigits_.data()); + exp_ = other.exp_; + } + + template FMT_CONSTEXPR void operator=(Int n) { + FMT_ASSERT(n > 0, ""); + assign(uint64_or_128_t(n)); + } + + FMT_CONSTEXPR auto num_bigits() const -> int { + return static_cast(bigits_.size()) + exp_; + } + + FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { + FMT_ASSERT(shift >= 0, ""); + exp_ += shift / bigit_bits; + shift %= bigit_bits; + if (shift == 0) return *this; + bigit carry = 0; + for (size_t i = 0, n = bigits_.size(); i < n; ++i) { + bigit c = bigits_[i] >> (bigit_bits - shift); + bigits_[i] = (bigits_[i] << shift) + carry; + carry = c; + } + if (carry != 0) bigits_.push_back(carry); + return *this; + } + + template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { + FMT_ASSERT(value > 0, ""); + multiply(uint32_or_64_or_128_t(value)); + return *this; + } + + friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { + int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); + if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; + int i = static_cast(b1.bigits_.size()) - 1; + int j = static_cast(b2.bigits_.size()) - 1; + int end = i - j; + if (end < 0) end = 0; + for (; i >= end; --i, --j) { + bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; + if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; + } + if (i != j) return i > j ? 1 : -1; + return 0; + } + + // Returns compare(lhs1 + lhs2, rhs). + friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) -> int { + int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); + int num_rhs_bigits = rhs.num_bigits(); + if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; + if (max_lhs_bigits > num_rhs_bigits) return 1; + double_bigit borrow = 0; + int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); + for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { + double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); + bigit rhs_bigit = rhs.get_bigit(i); + if (sum > rhs_bigit + borrow) return 1; + borrow = rhs_bigit + borrow - sum; + if (borrow > 1) return -1; + borrow <<= bigit_bits; + } + return borrow != 0 ? -1 : 0; + } + + // Assigns pow(10, exp) to this bigint. + FMT_CONSTEXPR20 void assign_pow10(int exp) { + FMT_ASSERT(exp >= 0, ""); + if (exp == 0) return *this = 1; + int bitmask = 1 << (num_bits() - + countl_zero(static_cast(exp)) - 1); + // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by + // repeated squaring and multiplication. + *this = 5; + bitmask >>= 1; + while (bitmask != 0) { + square(); + if ((exp & bitmask) != 0) *this *= 5; + bitmask >>= 1; + } + *this <<= exp; // Multiply by pow(2, exp) by shifting. + } + + FMT_CONSTEXPR20 void square() { + int num_bigits = static_cast(bigits_.size()); + int num_result_bigits = 2 * num_bigits; + basic_memory_buffer n(std::move(bigits_)); + bigits_.resize(to_unsigned(num_result_bigits)); + auto sum = uint128_t(); + for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { + // Compute bigit at position bigit_index of the result by adding + // cross-product terms n[i] * n[j] such that i + j == bigit_index. + for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { + // Most terms are multiplied twice which can be optimized in the future. + sum += double_bigit(n[i]) * n[j]; + } + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); // Compute the carry. + } + // Do the same for the top half. + for (int bigit_index = num_bigits; bigit_index < num_result_bigits; + ++bigit_index) { + for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) + sum += double_bigit(n[i++]) * n[j--]; + bigits_[bigit_index] = static_cast(sum); + sum >>= num_bits(); + } + remove_leading_zeros(); + exp_ *= 2; + } + + // If this bigint has a bigger exponent than other, adds trailing zero to make + // exponents equal. This simplifies some operations such as subtraction. + FMT_CONSTEXPR void align(const bigint& other) { + int exp_difference = exp_ - other.exp_; + if (exp_difference <= 0) return; + int num_bigits = static_cast(bigits_.size()); + bigits_.resize(to_unsigned(num_bigits + exp_difference)); + for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) + bigits_[j] = bigits_[i]; + fill_n(bigits_.data(), to_unsigned(exp_difference), 0U); + exp_ -= exp_difference; + } + + // Divides this bignum by divisor, assigning the remainder to this and + // returning the quotient. + FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { + FMT_ASSERT(this != &divisor, ""); + if (compare(*this, divisor) < 0) return 0; + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); + align(divisor); + int quotient = 0; + do { + subtract_aligned(divisor); + ++quotient; + } while (compare(*this, divisor) >= 0); + return quotient; + } +}; + +// format_dragon flags. +enum dragon { + predecessor_closer = 1, + fixup = 2, // Run fixup to correct exp10 which can be off by one. + fixed = 4, +}; + +// Formats a floating-point number using a variation of the Fixed-Precision +// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: +// https://fmt.dev/papers/p372-steele.pdf. +FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, + unsigned flags, int num_digits, + buffer& buf, int& exp10) { + bigint numerator; // 2 * R in (FPP)^2. + bigint denominator; // 2 * S in (FPP)^2. + // lower and upper are differences between value and corresponding boundaries. + bigint lower; // (M^- in (FPP)^2). + bigint upper_store; // upper's value if different from lower. + bigint* upper = nullptr; // (M^+ in (FPP)^2). + // Shift numerator and denominator by an extra bit or two (if lower boundary + // is closer) to make lower and upper integers. This eliminates multiplication + // by 2 during later computations. + bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; + int shift = is_predecessor_closer ? 2 : 1; + if (value.e >= 0) { + numerator = value.f; + numerator <<= value.e + shift; + lower = 1; + lower <<= value.e; + if (is_predecessor_closer) { + upper_store = 1; + upper_store <<= value.e + 1; + upper = &upper_store; + } + denominator.assign_pow10(exp10); + denominator <<= shift; + } else if (exp10 < 0) { + numerator.assign_pow10(-exp10); + lower.assign(numerator); + if (is_predecessor_closer) { + upper_store.assign(numerator); + upper_store <<= 1; + upper = &upper_store; + } + numerator *= value.f; + numerator <<= shift; + denominator = 1; + denominator <<= shift - value.e; + } else { + numerator = value.f; + numerator <<= shift; + denominator.assign_pow10(exp10); + denominator <<= shift - value.e; + lower = 1; + if (is_predecessor_closer) { + upper_store = 1ULL << 1; + upper = &upper_store; + } + } + int even = static_cast((value.f & 1) == 0); + if (!upper) upper = &lower; + bool shortest = num_digits < 0; + if ((flags & dragon::fixup) != 0) { + if (add_compare(numerator, *upper, denominator) + even <= 0) { + --exp10; + numerator *= 10; + if (num_digits < 0) { + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); + } + // Invariant: value == (numerator / denominator) * pow(10, exp10). + if (shortest) { + // Generate the shortest representation. + num_digits = 0; + char* data = buf.data(); + for (;;) { + int digit = numerator.divmod_assign(denominator); + bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. + // numerator + upper >[=] pow10: + bool high = add_compare(numerator, *upper, denominator) + even > 0; + data[num_digits++] = static_cast('0' + digit); + if (low || high) { + if (!low) { + ++data[num_digits - 1]; + } else if (high) { + int result = add_compare(numerator, numerator, denominator); + // Round half to even. + if (result > 0 || (result == 0 && (digit % 2) != 0)) + ++data[num_digits - 1]; + } + buf.try_resize(to_unsigned(num_digits)); + exp10 -= num_digits - 1; + return; + } + numerator *= 10; + lower *= 10; + if (upper != &lower) *upper *= 10; + } + } + // Generate the given number of digits. + exp10 -= num_digits - 1; + if (num_digits <= 0) { + auto digit = '0'; + if (num_digits == 0) { + denominator *= 10; + digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + } + buf.push_back(digit); + return; + } + buf.try_resize(to_unsigned(num_digits)); + for (int i = 0; i < num_digits - 1; ++i) { + int digit = numerator.divmod_assign(denominator); + buf[i] = static_cast('0' + digit); + numerator *= 10; + } + int digit = numerator.divmod_assign(denominator); + auto result = add_compare(numerator, numerator, denominator); + if (result > 0 || (result == 0 && (digit % 2) != 0)) { + if (digit == 9) { + const auto overflow = '0' + 10; + buf[num_digits - 1] = overflow; + // Propagate the carry. + for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] == overflow) { + buf[0] = '1'; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; + } + return; + } + ++digit; + } + buf[num_digits - 1] = static_cast('0' + digit); +} + +// Formats a floating-point number using the hexfloat format. +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + // float is passed as double to reduce the number of instantiations and to + // simplify implementation. + static_assert(!std::is_same::value, ""); + + using info = dragonbox::float_info; + + // Assume Float is in the format [sign][exponent][significand]. + using carrier_uint = typename info::carrier_uint; + + const auto num_float_significand_bits = detail::num_significand_bits(); + + basic_fp f(value); + f.e += num_float_significand_bits; + if (!has_implicit_bit()) --f.e; + + const auto num_fraction_bits = + num_float_significand_bits + (has_implicit_bit() ? 1 : 0); + const auto num_xdigits = (num_fraction_bits + 3) / 4; + + const auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_mask = carrier_uint(0xF) << leading_shift; + const auto leading_xdigit = + static_cast((f.f & leading_mask) >> leading_shift); + if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); + + int print_xdigits = num_xdigits - 1; + if (specs.precision >= 0 && print_xdigits > specs.precision) { + const int shift = ((print_xdigits - specs.precision - 1) * 4); + const auto mask = carrier_uint(0xF) << shift; + const auto v = static_cast((f.f & mask) >> shift); + + if (v >= 8) { + const auto inc = carrier_uint(1) << (shift + 4); + f.f += inc; + f.f &= ~(inc - 1); + } + + // Check long double overflow + if (!has_implicit_bit()) { + const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; + if ((f.f & implicit_bit) == implicit_bit) { + f.f >>= 4; + f.e += 4; + } + } + + print_xdigits = specs.precision; + } + + char xdigits[num_bits() / 4]; + detail::fill_n(xdigits, sizeof(xdigits), '0'); + format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); + + // Remove zero tail + while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; + + buf.push_back('0'); + buf.push_back(specs.upper() ? 'X' : 'x'); + buf.push_back(xdigits[0]); + if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) + buf.push_back('.'); + buf.append(xdigits + 1, xdigits + 1 + print_xdigits); + for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); + + buf.push_back(specs.upper() ? 'P' : 'p'); + + uint32_t abs_e; + if (f.e < 0) { + buf.push_back('-'); + abs_e = static_cast(-f.e); + } else { + buf.push_back('+'); + abs_e = static_cast(f.e); + } + format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); +} + +template ::value)> +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + format_hexfloat(static_cast(value), specs, buf); +} + +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; +} + +template +FMT_CONSTEXPR20 auto format_float(Float value, int precision, + const format_specs& specs, bool binary32, + buffer& buf) -> int { + // float is passed as double to reduce the number of instantiations. + static_assert(!std::is_same::value, ""); + auto converted_value = convert_float(value); + + const bool fixed = specs.type() == presentation_type::fixed; + if (value == 0) { + if (precision <= 0 || !fixed) { + buf.push_back('0'); + return 0; + } + buf.try_resize(to_unsigned(precision)); + fill_n(buf.data(), precision, '0'); + return -precision; + } + + int exp = 0; + bool use_dragon = true; + unsigned dragon_flags = 0; + if (!is_fast_float() || is_constant_evaluated()) { + const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) + using info = dragonbox::float_info; + const auto f = basic_fp(converted_value); + // Compute exp, an approximate power of 10, such that + // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). + // This is based on log10(value) == log2(value) / log2(10) and approximation + // of log2(value) by e + num_fraction_bits idea from double-conversion. + auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10; + exp = static_cast(e); + if (e > exp) ++exp; // Compute ceil. + dragon_flags = dragon::fixup; + } else { + // Extract significand bits and exponent bits. + using info = dragonbox::float_info; + auto br = bit_cast(static_cast(value)); + + const uint64_t significand_mask = + (static_cast(1) << num_significand_bits()) - 1; + uint64_t significand = (br & significand_mask); + int exponent = static_cast((br & exponent_mask()) >> + num_significand_bits()); + + if (exponent != 0) { // Check if normal. + exponent -= exponent_bias() + num_significand_bits(); + significand |= + (static_cast(1) << num_significand_bits()); + significand <<= 1; + } else { + // Normalize subnormal inputs. + FMT_ASSERT(significand != 0, "zeros should not appear here"); + int shift = countl_zero(significand); + FMT_ASSERT(shift >= num_bits() - num_significand_bits(), + ""); + shift -= (num_bits() - num_significand_bits() - 2); + exponent = (std::numeric_limits::min_exponent - + num_significand_bits()) - + shift; + significand <<= shift; + } + + // Compute the first several nonzero decimal significand digits. + // We call the number we get the first segment. + const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); + exp = -k; + const int beta = exponent + dragonbox::floor_log2_pow10(k); + uint64_t first_segment; + bool has_more_segments; + int digits_in_the_first_segment; + { + const auto r = dragonbox::umul192_upper128( + significand << beta, dragonbox::get_cached_power(k)); + first_segment = r.high(); + has_more_segments = r.low() != 0; + + // The first segment can have 18 ~ 19 digits. + if (first_segment >= 1000000000000000000ULL) { + digits_in_the_first_segment = 19; + } else { + // When it is of 18-digits, we align it to 19-digits by adding a bogus + // zero at the end. + digits_in_the_first_segment = 18; + first_segment *= 10; + } + } + + // Compute the actual number of decimal digits to print. + if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment); + + // Use Dragon4 only when there might be not enough digits in the first + // segment. + if (digits_in_the_first_segment > precision) { + use_dragon = false; + + if (precision <= 0) { + exp += digits_in_the_first_segment; + + if (precision < 0) { + // Nothing to do, since all we have are just leading zeros. + buf.try_resize(0); + } else { + // We may need to round-up. + buf.try_resize(1); + if ((first_segment | static_cast(has_more_segments)) > + 5000000000000000000ULL) { + buf[0] = '1'; + } else { + buf[0] = '0'; + } + } + } // precision <= 0 + else { + exp += digits_in_the_first_segment - precision; + + // When precision > 0, we divide the first segment into three + // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits + // in 32-bits which usually allows faster calculation than in + // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize + // division-by-constant for large 64-bit divisors, we do it here + // manually. The magic number 7922816251426433760 below is equal to + // ceil(2^(64+32) / 10^10). + const uint32_t first_subsegment = static_cast( + dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> + 32); + const uint64_t second_third_subsegments = + first_segment - first_subsegment * 10000000000ULL; + + uint64_t prod; + uint32_t digits; + bool should_round_up; + int number_of_digits_to_print = min_of(precision, 9); + + // Print a 9-digits subsegment, either the first or the second. + auto print_subsegment = [&](uint32_t subsegment, char* buffer) { + int number_of_digits_printed = 0; + + // If we want to print an odd number of digits from the subsegment, + if ((number_of_digits_to_print & 1) != 0) { + // Convert to 64-bit fixed-point fractional form with 1-digit + // integer part. The magic number 720575941 is a good enough + // approximation of 2^(32 + 24) / 10^8; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(720575941)) >> 24) + 1; + digits = static_cast(prod >> 32); + *buffer = static_cast('0' + digits); + number_of_digits_printed++; + } + // If we want to print an even number of digits from the + // first_subsegment, + else { + // Convert to 64-bit fixed-point fractional form with 2-digits + // integer part. The magic number 450359963 is a good enough + // approximation of 2^(32 + 20) / 10^7; see + // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case + // for details. + prod = ((subsegment * static_cast(450359963)) >> 20) + 1; + digits = static_cast(prod >> 32); + write2digits(buffer, digits); + number_of_digits_printed += 2; + } + + // Print all digit pairs. + while (number_of_digits_printed < number_of_digits_to_print) { + prod = static_cast(prod) * static_cast(100); + digits = static_cast(prod >> 32); + write2digits(buffer + number_of_digits_printed, digits); + number_of_digits_printed += 2; + } + }; + + // Print first subsegment. + print_subsegment(first_subsegment, buf.data()); + + // Perform rounding if the first subsegment is the last subsegment to + // print. + if (precision <= 9) { + // Rounding inside the subsegment. + // We round-up if: + // - either the fractional part is strictly larger than 1/2, or + // - the fractional part is exactly 1/2 and the last digit is odd. + // We rely on the following observations: + // - If fractional_part >= threshold, then the fractional part is + // strictly larger than 1/2. + // - If the MSB of fractional_part is set, then the fractional part + // must be at least 1/2. + // - When the MSB of fractional_part is set, either + // second_third_subsegments being nonzero or has_more_segments + // being true means there are further digits not printed, so the + // fractional part is strictly larger than 1/2. + if (precision < 9) { + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + // In this case, the fractional part is at least 1/2 if and only if + // second_third_subsegments >= 5000000000ULL, and is strictly larger + // than 1/2 if we further have either second_third_subsegments > + // 5000000000ULL or has_more_segments == true. + else { + should_round_up = second_third_subsegments > 5000000000ULL || + (second_third_subsegments == 5000000000ULL && + ((digits & 1) != 0 || has_more_segments)); + } + } + // Otherwise, print the second subsegment. + else { + // Compilers are not aware of how to leverage the maximum value of + // second_third_subsegments to find out a better magic number which + // allows us to eliminate an additional shift. 1844674407370955162 = + // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). + const uint32_t second_subsegment = + static_cast(dragonbox::umul128_upper64( + second_third_subsegments, 1844674407370955162ULL)); + const uint32_t third_subsegment = + static_cast(second_third_subsegments) - + second_subsegment * 10; + + number_of_digits_to_print = precision - 9; + print_subsegment(second_subsegment, buf.data() + 9); + + // Rounding inside the subsegment. + if (precision < 18) { + // The condition third_subsegment != 0 implies that the segment was + // of 19 digits, so in this case the third segment should be + // consisting of a genuine digit from the input. + uint32_t fractional_part = static_cast(prod); + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; + } + // Rounding at the subsegment boundary. + else { + // In this case, the segment must be of 19 digits, thus + // the third subsegment should be consisting of a genuine digit from + // the input. + should_round_up = third_subsegment > 5 || + (third_subsegment == 5 && + ((digits & 1) != 0 || has_more_segments)); + } + } + + // Round-up if necessary. + if (should_round_up) { + ++buf[precision - 1]; + for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { + buf[i] = '0'; + ++buf[i - 1]; + } + if (buf[0] > '9') { + buf[0] = '1'; + if (fixed) + buf[precision++] = '0'; + else + ++exp; + } + } + buf.try_resize(to_unsigned(precision)); + } + } // if (digits_in_the_first_segment > precision) + else { + // Adjust the exponent for its use in Dragon4. + exp += digits_in_the_first_segment - 1; + } + } + if (use_dragon) { + auto f = basic_fp(); + bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) + : f.assign(converted_value); + if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; + if (fixed) dragon_flags |= dragon::fixed; + // Limit precision to the maximum possible number of significant digits in + // an IEEE754 double because we don't need to generate zeros. + const int max_double_digits = 767; + if (precision > max_double_digits) precision = max_double_digits; + format_dragon(f, dragon_flags, precision, buf, exp); + } + if (!fixed && !specs.alt()) { + // Remove trailing zeros. + auto num_digits = buf.size(); + while (num_digits > 0 && buf[num_digits - 1] == '0') { + --num_digits; + ++exp; + } + buf.try_resize(num_digits); + } + return exp; +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + + // Use signbit because value < 0 is false for NaN. + sign s = detail::signbit(value) ? sign::minus : specs.sign(); + + if (!detail::isfinite(value)) + return write_nonfinite(out, detail::isnan(value), specs, s); + + if (specs.align() == align::numeric && s != sign::none) { + *out++ = detail::getsign(s); + s = sign::none; + if (specs.width != 0) --specs.width; + } + + const int exp_upper = detail::exp_upper(); + int precision = specs.precision; + if (precision < 0) { + if (specs.type() != presentation_type::none) { + precision = 6; + } else if (is_fast_float::value && !is_constant_evaluated()) { + // Use Dragonbox for the shortest format. + auto dec = dragonbox::to_decimal(static_cast>(value)); + return write_float(out, dec, specs, s, exp_upper, loc); + } + } + + memory_buffer buffer; + if (specs.type() == presentation_type::hexfloat) { + if (s != sign::none) buffer.push_back(detail::getsign(s)); + format_hexfloat(convert_float(value), specs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); + } + + if (specs.type() == presentation_type::exp) { + if (precision == max_value()) + report_error("number is too big"); + else + ++precision; + if (specs.precision != 0) specs.set_alt(); + } else if (specs.type() == presentation_type::fixed) { + if (specs.precision != 0) specs.set_alt(); + } else if (precision == 0) { + precision = 1; + } + int exp = format_float(convert_float(value), precision, specs, + std::is_same(), buffer); + + specs.precision = precision; + auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; + return write_float(out, f, specs, s, exp_upper, loc); +} + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { + if (is_constant_evaluated()) return write(out, value, format_specs()); + + auto s = detail::signbit(value) ? sign::minus : sign::none; + auto mask = exponent_mask>(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), {}, s); + + auto dec = dragonbox::to_decimal(static_cast>(value)); + auto significand = dec.significand; + int significand_size = count_digits(significand); + int exponent = dec.exponent + significand_size - 1; + if (use_fixed(exponent, detail::exp_upper())) { + return write_fixed>( + out, dec, significand_size, Char('.'), {}, s); + } + + // Write value in the exponential format. + const char* prefix = "e+"; + int abs_exponent = exponent; + if (exponent < 0) { + abs_exponent = -exponent; + prefix = "e-"; + } + auto has_decimal_point = significand_size != 1; + size_t size = std::is_pointer::value + ? 0u + : to_unsigned((s != sign::none ? 1 : 0) + significand_size + + (has_decimal_point ? 1 : 0) + + (abs_exponent >= 100 ? 5 : 4)); + if (auto ptr = to_pointer(out, size)) { + if (s != sign::none) *ptr++ = Char('-'); + if (has_decimal_point) { + auto begin = ptr; + ptr = format_decimal(ptr, significand, significand_size + 1); + *begin = begin[1]; + begin[1] = '.'; + } else { + *ptr++ = static_cast('0' + significand); + } + if (std::is_same::value) { + memcpy(ptr, prefix, 2); + ptr += 2; + } else { + *ptr++ = prefix[0]; + *ptr++ = prefix[1]; + } + if (abs_exponent >= 100) { + *ptr++ = static_cast('0' + abs_exponent / 100); + abs_exponent %= 100; + } + write2digits(ptr, static_cast(abs_exponent)); + return select::value>(ptr + 2, out); + } + auto it = reserve(out, size); + if (s != sign::none) *it++ = Char('-'); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, significand, significand_size, 1, + has_decimal_point ? Char('.') : Char()); + *it++ = Char('e'); + it = write_exponent(exponent, it); + return base_iterator(out, it); +} + +template ::value && + !is_fast_float::value)> +inline auto write(OutputIt out, T value) -> OutputIt { + return write(out, value, {}); +} + +template +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) + -> OutputIt { + FMT_ASSERT(false, ""); + return out; +} + +template +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) + -> OutputIt { + return copy_noinline(value.begin(), value.end(), out); +} + +template ::value)> +constexpr auto write(OutputIt out, const T& value) -> OutputIt { + return write(out, to_string_view(value)); +} + +// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. +template < + typename Char, typename OutputIt, typename T, + bool check = std::is_enum::value && !std::is_same::value && + mapped_type_constant::value != type::custom_type, + FMT_ENABLE_IF(check)> +FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { + return write(out, static_cast>(value)); +} + +template ::value)> +FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type() != presentation_type::none && + specs.type() != presentation_type::string + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); +} + +template +FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { + auto it = reserve(out, 1); + *it++ = value; + return base_iterator(out, it); +} + +template +FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { + if (value) return write(out, basic_string_view(value)); + report_error("string pointer is null"); + return out; +} + +template ::value)> +auto write(OutputIt out, const T* value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return write_ptr(out, bit_cast(value), &specs); +} + +template ::value == + type::custom_type && + !std::is_fundamental::value)> +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { + auto f = formatter(); + auto parse_ctx = parse_context({}); + f.parse(parse_ctx); + auto ctx = basic_format_context(out, {}, {}); + return f.format(value, ctx); +} + +template +using is_builtin = + bool_constant::value || FMT_BUILTIN_TYPES>; + +// An argument visitor that formats the argument and writes it via the output +// iterator. It's a class and not a generic lambda for compatibility with C++11. +template struct default_arg_formatter { + using context = buffered_context; + + basic_appender out; + + void operator()(monostate) { report_error("argument not found"); } + + template ::value)> + void operator()(T value) { + write(out, value); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg::handle h) { + // Use a null locale since the default format must be unlocalized. + auto parse_ctx = parse_context({}); + auto format_ctx = context(out, {}, {}); + h.format(parse_ctx, format_ctx); + } +}; + +template struct arg_formatter { + basic_appender out; + const format_specs& specs; + FMT_NO_UNIQUE_ADDRESS locale_ref locale; + + template ::value)> + FMT_CONSTEXPR FMT_INLINE void operator()(T value) { + detail::write(out, value, specs, locale); + } + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg>::handle) { + // User-defined types are handled separately because they require access + // to the parse context. + } +}; + +struct dynamic_spec_getter { + template ::value)> + FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { + return is_negative(value) ? ~0ull : static_cast(value); + } + + template ::value)> + FMT_CONSTEXPR auto operator()(T) -> unsigned long long { + report_error("width/precision is not integer"); + return 0; + } +}; + +template +FMT_CONSTEXPR void handle_dynamic_spec( + arg_id_kind kind, int& value, + const arg_ref& ref, Context& ctx) { + if (kind == arg_id_kind::none) return; + auto arg = + kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); + if (!arg) report_error("argument not found"); + unsigned long long result = arg.visit(dynamic_spec_getter()); + if (result > to_unsigned(max_value())) + report_error("width/precision is out of range"); + value = static_cast(result); +} + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template Str> +struct static_named_arg : view { + static constexpr auto name = Str.data; + + const T& value; + static_named_arg(const T& v) : value(v) {} +}; + +template Str> +struct is_named_arg> : std::true_type {}; + +template Str> +struct is_static_named_arg> : std::true_type { +}; + +template Str> +struct udl_arg { + template auto operator=(T&& value) const { + return static_named_arg(std::forward(value)); + } +}; +#else +template struct udl_arg { + const Char* str; + + template auto operator=(T&& value) const -> named_arg { + return {str, std::forward(value)}; + } +}; +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS + +template struct format_handler { + parse_context parse_ctx; + buffered_context ctx; + + void on_text(const Char* begin, const Char* end) { + copy_noinline(begin, end, ctx.out()); + } + + FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + parse_ctx.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + parse_ctx.check_arg_id(id); + int arg_id = ctx.arg_id(id); + if (arg_id < 0) report_error("argument not found"); + return arg_id; + } + + FMT_INLINE void on_replacement_field(int id, const Char*) { + ctx.arg(id).visit(default_arg_formatter{ctx.out()}); + } + + auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + auto arg = ctx.arg(id); + if (!arg) report_error("argument not found"); + // Not using a visitor for custom types gives better codegen. + if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin(); + + auto specs = dynamic_format_specs(); + begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type()); + if (specs.dynamic()) { + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + arg.visit(arg_formatter{ctx.out(), specs, ctx.locale()}); + return begin; + } + + FMT_NORETURN void on_error(const char* message) { report_error(message); } +}; + +// It is used in format-inl.h and os.cc. +using format_func = void (*)(detail::buffer&, int, const char*); +FMT_API void do_report_error(format_func func, int error_code, + const char* message) noexcept; + +FMT_API void format_error_code(buffer& out, int error_code, + string_view message) noexcept; + +template +template +FMT_CONSTEXPR auto native_formatter::format( + const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { + if (!specs_.dynamic()) + return write(ctx.out(), val, specs_, ctx.locale()); + auto specs = format_specs(specs_); + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs_.precision_ref, ctx); + return write(ctx.out(), val, specs, ctx.locale()); +} +} // namespace detail + +FMT_BEGIN_EXPORT + +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + locale_ref loc_; + + public: + using char_type = Char; + using iterator = OutputIt; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + constexpr generic_context(OutputIt out, + basic_format_args args, + locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; + + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) const + -> basic_format_arg { + return args_.get(name); + } + constexpr auto arg_id(basic_string_view name) const -> int { + return args_.get_id(name); + } + + constexpr auto out() const -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + constexpr auto locale() const -> locale_ref { return loc_; } +}; + +class loc_value { + private: + basic_format_arg value_; + + public: + template ::value)> + loc_value(T value) : value_(value) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", std::string grouping = "\3", + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(grouping), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); + } +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template \ + struct formatter : formatter { \ + template \ + FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter::format(value, ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, detail::long_type); +FMT_FORMAT_AS(unsigned long, detail::ulong_type); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(detail::std_string_view, basic_string_view); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(void*, const void*); + +template +struct formatter : formatter, Char> {}; + +template +class formatter, Char> + : public formatter, Char> {}; + +template +struct formatter, Char> : formatter {}; +template +struct formatter, Char> + : formatter {}; + +template +struct formatter + : detail::native_formatter {}; + +template +struct formatter>> + : formatter, Char> { + template + FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto&& val = format_as(value); // Make an lvalue reference for format. + return formatter, Char>::format(val, ctx); + } +}; + +/** + * Converts `p` to `const void*` for pointer formatting. + * + * **Example**: + * + * auto s = fmt::format("{}", fmt::ptr(p)); + */ +template auto ptr(T p) -> const void* { + static_assert(std::is_pointer::value, "fmt::ptr used with non-pointer"); + return detail::bit_cast(p); +} + +/** + * Converts `e` to the underlying type. + * + * **Example**: + * + * enum class color { red, green, blue }; + * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" + */ +template +constexpr auto underlying(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} + +namespace enums { +template ::value)> +constexpr auto format_as(Enum e) noexcept -> underlying_t { + return static_cast>(e); +} +} // namespace enums + +#ifdef __cpp_lib_byte +template +struct formatter : formatter { + static auto format_as(std::byte b) -> unsigned char { + return static_cast(b); + } + template + auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { + return formatter::format(format_as(b), ctx); + } +}; +#endif + +struct bytes { + string_view data; + + inline explicit bytes(string_view s) : data(s) {} +}; + +template <> struct formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::string_type); + } + + template + auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + return detail::write_bytes(ctx.out(), b.data, specs); + } +}; + +// group_digits_view is not derived from view because it copies the argument. +template struct group_digits_view { + T value; +}; + +/** + * Returns a view that formats an integer value using ',' as a + * locale-independent thousands separator. + * + * **Example**: + * + * fmt::print("{}", fmt::group_digits(12345)); + * // Output: "12,345" + */ +template auto group_digits(T value) -> group_digits_view { + return {value}; +} + +template struct formatter> : formatter { + private: + detail::dynamic_format_specs<> specs_; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type::int_type); + } + + template + auto format(group_digits_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + auto arg = detail::make_write_int_arg(view.value, specs.sign()); + return detail::write_int( + ctx.out(), static_cast>(arg.abs_value), + arg.prefix, specs, detail::digit_grouping("\3", ",")); + } +}; + +template struct nested_view { + const formatter* fmt; + const T* value; +}; + +template +struct formatter, Char> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + template + auto format(nested_view view, FormatContext& ctx) const + -> decltype(ctx.out()) { + return view.fmt->format(*view.value, ctx); + } +}; + +template struct nested_formatter { + private: + basic_specs specs_; + int width_; + formatter formatter_; + + public: + constexpr nested_formatter() : width_(0) {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + auto specs = format_specs(); + it = detail::parse_align(it, end, specs); + specs_ = specs; + Char c = *it; + auto width_ref = detail::arg_ref(); + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs, width_ref, ctx); + width_ = specs.width; + } + ctx.advance_to(it); + return formatter_.parse(ctx); + } + + template + auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) { + if (width_ == 0) return write(ctx.out()); + auto buf = basic_memory_buffer(); + write(basic_appender(buf)); + auto specs = format_specs(); + specs.width = width_; + specs.copy_fill_from(specs_); + specs.set_align(specs_.align()); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + auto nested(const T& value) const -> nested_view { + return nested_view{&formatter_, &value}; + } +}; + +inline namespace literals { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template constexpr auto operator""_a() { + using char_t = remove_cvref_t; + return detail::udl_arg(); +} +#else +/** + * User-defined literal equivalent of `fmt::arg`. + * + * **Example**: + * + * using namespace fmt::literals; + * fmt::print("The answer is {answer}.", "answer"_a=42); + */ +constexpr auto operator""_a(const char* s, size_t) -> detail::udl_arg { + return {s}; +} +#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS +} // namespace literals + +/// A fast integer formatter. +class format_int { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum { buffer_size = std::numeric_limits::digits10 + 3 }; + mutable char buffer_[buffer_size]; + char* str_; + + template + FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* { + auto n = static_cast>(value); + return detail::do_format_decimal(buffer_, n, buffer_size - 1); + } + + template + FMT_CONSTEXPR20 auto format_signed(Int value) -> char* { + auto abs_value = static_cast>(value); + bool negative = value < 0; + if (negative) abs_value = 0 - abs_value; + auto begin = format_unsigned(abs_value); + if (negative) *--begin = '-'; + return begin; + } + + public: + FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long long value) + : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long value) + : str_(format_unsigned(value)) {} + FMT_CONSTEXPR20 explicit format_int(unsigned long long value) + : str_(format_unsigned(value)) {} + + /// Returns the number of characters written to the output buffer. + FMT_CONSTEXPR20 auto size() const -> size_t { + return detail::to_unsigned(buffer_ - str_ + buffer_size - 1); + } + + /// Returns a pointer to the output buffer content. No terminating null + /// character is appended. + FMT_CONSTEXPR20 auto data() const -> const char* { return str_; } + + /// Returns a pointer to the output buffer content with terminating null + /// character appended. + FMT_CONSTEXPR20 auto c_str() const -> const char* { + buffer_[buffer_size - 1] = '\0'; + return str_; + } + + /// Returns the content of the output buffer as an `std::string`. + inline auto str() const -> std::string { return {str_, size()}; } +}; + +#if FMT_CLANG_ANALYZER +# define FMT_STRING_IMPL(s, base) s +#else +# define FMT_STRING_IMPL(s, base) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type = fmt::remove_cvref_t; \ + constexpr explicit operator fmt::basic_string_view() \ + const { \ + return fmt::detail::compile_string_to_view(s); \ + } \ + }; \ + using FMT_STRING_VIEW = \ + fmt::basic_string_view; \ + fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ + return FMT_COMPILE_STRING(); \ + }() +#endif // FMT_CLANG_ANALYZER + +/** + * Constructs a legacy compile-time format string from a string literal `s`. + * + * **Example**: + * + * // A compile-time error because 'd' is an invalid specifier for strings. + * std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); + */ +#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) + +FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error; + +/** + * Constructs `std::system_error` with a message formatted with + * `fmt::format(fmt, args...)`. + * `error_code` is a system error code as given by `errno`. + * + * **Example**: + * + * // This throws std::system_error with the description + * // cannot open file 'madeup': No such file or directory + * // or similar (system message may vary). + * const char* filename = "madeup"; + * FILE* file = fopen(filename, "r"); + * if (!file) + * throw fmt::system_error(errno, "cannot open file '{}'", filename); + */ +template +auto system_error(int error_code, format_string fmt, T&&... args) + -> std::system_error { + return vsystem_error(error_code, fmt.str, vargs{{args...}}); +} + +/** + * Formats an error message for an error returned by an operating system or a + * language runtime, for example a file opening error, and writes it to `out`. + * The format is the same as the one used by `std::system_error(ec, message)` + * where `ec` is `std::error_code(error_code, std::generic_category())`. + * It is implementation-defined but normally looks like: + * + * : + * + * where `` is the passed message and `` is the system + * message corresponding to the error code. + * `error_code` is a system error code as given by `errno`. + */ +FMT_API void format_system_error(detail::buffer& out, int error_code, + const char* message) noexcept; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, const char* message) noexcept; + +inline auto vformat(locale_ref loc, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, fmt, args, loc); + return {buf.data(), buf.size()}; +} + +template +FMT_INLINE auto format(locale_ref loc, format_string fmt, T&&... args) + -> std::string { + return vformat(loc, fmt.str, vargs{{args...}}); +} + +template ::value)> +auto vformat_to(OutputIt out, locale_ref loc, string_view fmt, format_args args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, loc); + return detail::get_iterator(buf, out); +} + +template ::value)> +FMT_INLINE auto format_to(OutputIt out, locale_ref loc, format_string fmt, + T&&... args) -> OutputIt { + return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); +} + +template +FMT_NODISCARD FMT_INLINE auto formatted_size(locale_ref loc, + format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); + return buf.count(); +} + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; + +/** + * Formats `args` according to specifications in `fmt` and returns the result + * as a string. + * + * **Example**: + * + * #include + * std::string message = fmt::format("The answer is {}.", 42); + */ +template +FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) + -> std::string { + return vformat(fmt.str, vargs{{args...}}); +} + +/** + * Converts `value` to `std::string` using the default format for type `T`. + * + * **Example**: + * + * std::string answer = fmt::to_string(42); + */ +template ::value)> +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(T value) -> std::string { + // The buffer should be large enough to store the number including the sign + // or "false" for bool. + char buffer[max_of(detail::digits10() + 2, 5)]; + return {buffer, detail::write(buffer, value)}; +} + +template ::value)> +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) + -> std::string { + return to_string(format_as(value)); +} + +template ::value && + !detail::use_format_as::value)> +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) + -> std::string { + auto buffer = memory_buffer(); + detail::write(appender(buffer), value); + return {buffer.data(), buffer.size()}; +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format-inl.h" +#endif + +// Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. +#ifdef FMT_REMOVE_TRANSITIVE_INCLUDES +# undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +#endif + +#endif // FMT_FORMAT_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/os.h b/lib/spdlog/include/spdlog/fmt/bundled/os.h new file mode 100644 index 0000000..94d730d --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/os.h @@ -0,0 +1,427 @@ +// 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 "format.h" + +#ifndef FMT_MODULE +# include +# include +# include +# include // std::system_error + +# if FMT_HAS_INCLUDE() +# include // LC_NUMERIC_MASK on macOS +# endif +#endif // FMT_MODULE + +#ifndef FMT_USE_FCNTL +// UWP doesn't provide _pipe. +# if FMT_HAS_INCLUDE("winapifamily.h") +# include +# endif +# if (FMT_HAS_INCLUDE() || defined(__APPLE__) || \ + defined(__linux__)) && \ + (!defined(WINAPI_FAMILY) || \ + (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \ + !defined(__wasm__) +# include // for O_RDONLY +# define FMT_USE_FCNTL 1 +# else +# define FMT_USE_FCNTL 0 +# endif +#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_HAS_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_BEGIN_EXPORT + +/** + * 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 | + * +---------------+-----------------------------+ + * | wcstring_view | basic_cstring_view | + * +---------------+-----------------------------+ + * + * This class is most useful as a parameter type for functions that wrap C APIs. + */ +template 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) {} + + /// Constructs a string reference from an `std::string` object. + basic_cstring_view(const std::basic_string& s) : data_(s.c_str()) {} + + /// Returns the pointer to a C string. + auto c_str() const -> const Char* { return data_; } +}; + +using cstring_view = basic_cstring_view; +using wcstring_view = basic_cstring_view; + +#ifdef _WIN32 +FMT_API const std::error_category& system_category() noexcept; + +namespace detail { +FMT_API void format_windows_error(buffer& out, int error_code, + const char* message) noexcept; +} + +FMT_API std::system_error vwindows_error(int error_code, string_view fmt, + format_args args); + +/** + * Constructs a `std::system_error` object with the description of the form + * + * : + * + * where `` is the formatted message and `` 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 'foo': The system cannot find the file specified. + * // or similar (system message may vary) if the file doesn't exist. + * const char *filename = "foo"; + * LPOFSTRUCT of = LPOFSTRUCT(); + * HFILE file = OpenFile(filename, &of, OF_READ); + * if (file == HFILE_ERROR) { + * throw fmt::windows_error(GetLastError(), + * "cannot open file '{}'", filename); + * } + */ +template +auto windows_error(int error_code, string_view message, const T&... args) + -> std::system_error { + return vwindows_error(error_code, message, vargs{{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) noexcept; +#else +inline auto system_category() noexcept -> const std::error_category& { + return std::system_category(); +} +#endif // _WIN32 + +// std::system is not available on some platforms such as iOS (#2248). +#ifdef __OSX__ +template > +void say(const S& fmt, Args&&... args) { + std::system(format("say \"{}\"", format(fmt, args...)).c_str()); +} +#endif + +// A buffered file. +class buffered_file { + private: + FILE* file_; + + friend class file; + + inline 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. + inline buffered_file() noexcept : file_(nullptr) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() noexcept; + + public: + inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) { + other.file_ = nullptr; + } + + inline auto operator=(buffered_file&& other) -> buffered_file& { + 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. + inline auto get() const noexcept -> FILE* { return file_; } + + FMT_API auto descriptor() const -> int; + + template + inline void print(string_view fmt, const T&... args) { + fmt::vargs vargs = {{args...}}; + detail::is_locking() ? fmt::vprint_buffered(file_, fmt, vargs) + : fmt::vprint(file_, fmt, vargs); + } +}; + +#if FMT_USE_FCNTL + +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with 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 FMT_API file { + private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) : fd_(fd) {} + + friend struct pipe; + + 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. + inline file() noexcept : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + file(cstring_view path, int oflag); + + public: + file(const file&) = delete; + void operator=(const file&) = delete; + + inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } + + // Move assignment is not noexcept because close may throw. + inline auto operator=(file&& other) -> file& { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Destroys the object closing the file it represents if any. + ~file() noexcept; + + // Returns the file descriptor. + inline auto descriptor() const noexcept -> int { return fd_; } + + // Closes the file. + void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + auto size() const -> long long; + + // Attempts to read count bytes from the file into the specified buffer. + auto read(void* buffer, size_t count) -> size_t; + + // Attempts to write count bytes from the specified buffer to the file. + auto write(const void* buffer, size_t count) -> size_t; + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + static auto dup(int fd) -> file; + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd, std::error_code& ec) noexcept; + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + auto fdopen(const char* mode) -> buffered_file; + +# if defined(_WIN32) && !defined(__MINGW32__) + // Opens a file and constructs a file object representing this file by + // wcstring_view filename. Windows only. + static file open_windows_file(wcstring_view path, int oflag); +# endif +}; + +struct FMT_API pipe { + file read_end; + file write_end; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + pipe(); +}; + +// Returns the memory page size. +auto getpagesize() -> long; + +namespace detail { + +struct buffer_size { + constexpr buffer_size() = default; + size_t value = 0; + FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size { + 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; + + constexpr ostream_params() {} + + template + ostream_params(T... params, int new_oflag) : ostream_params(params...) { + oflag = new_oflag; + } + + template + ostream_params(T... params, detail::buffer_size bs) + : ostream_params(params...) { + this->buffer_size = bs.value; + } + +// Intel has a bug that results in failure to deduce a constructor +// for empty parameter packs. +# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000 + ostream_params(int new_oflag) : oflag(new_oflag) {} + ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {} +# endif +}; + +} // namespace detail + +FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size(); + +/// A fast buffered output stream for writing from a single thread. Writing from +/// multiple threads without external synchronization may result in a data race. +class ostream : private detail::buffer { + private: + file file_; + + FMT_API ostream(cstring_view path, const detail::ostream_params& params); + + FMT_API static void grow(buffer& buf, size_t); + + public: + FMT_API ostream(ostream&& other) noexcept; + FMT_API ~ostream(); + + operator writer() { + detail::buffer& buf = *this; + return buf; + } + + inline void flush() { + if (size() == 0) return; + file_.write(data(), size() * sizeof(data()[0])); + clear(); + } + + template + friend auto output_file(cstring_view path, T... params) -> ostream; + + inline void close() { + flush(); + file_.close(); + } + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + vformat_to(appender(*this), fmt.str, vargs{{args...}}); + } +}; + +/** + * Opens a file for writing. Supported parameters passed in `params`: + * + * - ``: Flags passed to [open]( + * https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html) + * (`file::WRONLY | file::CREATE | file::TRUNC` by default) + * - `buffer_size=`: Output buffer size + * + * **Example**: + * + * auto out = fmt::output_file("guide.txt"); + * out.print("Don't {}", "Panic"); + */ +template +inline auto output_file(cstring_view path, T... params) -> ostream { + return {path, detail::ostream_params(params...)}; +} +#endif // FMT_USE_FCNTL + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_OS_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/ostream.h b/lib/spdlog/include/spdlog/fmt/bundled/ostream.h new file mode 100644 index 0000000..bf2371b --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/ostream.h @@ -0,0 +1,167 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#ifndef FMT_MODULE +# include // std::filebuf +#endif + +#ifdef _WIN32 +# ifdef __GLIBCXX__ +# include +# include +# endif +# include +#endif + +#include "chrono.h" // formatbuf + +#ifdef _MSVC_STL_UPDATE +# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE +#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 +# define FMT_MSVC_STL_UPDATE _MSVC_LANG +#else +# define FMT_MSVC_STL_UPDATE 0 +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +// Generate a unique explicit instantiation in every translation unit using a +// tag type in an anonymous namespace. +namespace { +struct file_access_tag {}; +} // namespace +template +class file_access { + friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } +}; + +#if FMT_MSVC_STL_UPDATE +template class file_access; +auto get_file(std::filebuf&) -> FILE*; +#endif + +// Write the content of buf to os. +// It is a separate function rather than a part of vprint to simplify testing. +template +void write_buffer(std::basic_ostream& os, buffer& buf) { + const Char* buf_data = buf.data(); + using unsigned_streamsize = make_unsigned_t; + unsigned_streamsize size = buf.size(); + unsigned_streamsize max_size = to_unsigned(max_value()); + do { + unsigned_streamsize n = size <= max_size ? size : max_size; + os.write(buf_data, static_cast(n)); + buf_data += n; + size -= n; + } while (size != 0); +} + +template struct streamed_view { + const T& value; +}; +} // namespace detail + +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct basic_ostream_formatter : formatter, Char> { + void set_debug_format() = delete; + + template + auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) { + auto buffer = basic_memory_buffer(); + auto&& formatbuf = detail::formatbuf>(buffer); + auto&& output = std::basic_ostream(&formatbuf); + output.imbue(std::locale::classic()); // The default is always unlocalized. + output << value; + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + return formatter, Char>::format( + {buffer.data(), buffer.size()}, ctx); + } +}; + +using ostream_formatter = basic_ostream_formatter; + +template +struct formatter, Char> + : basic_ostream_formatter { + template + auto format(detail::streamed_view view, Context& ctx) const + -> decltype(ctx.out()) { + return basic_ostream_formatter::format(view.value, ctx); + } +}; + +/** + * Returns a view that formats `value` via an ostream `operator<<`. + * + * **Example**: + * + * fmt::print("Current thread id: {}\n", + * fmt::streamed(std::this_thread::get_id())); + */ +template +constexpr auto streamed(const T& value) -> detail::streamed_view { + return {value}; +} + +inline void vprint(std::ostream& os, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + FILE* f = nullptr; +#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI + if (auto* buf = dynamic_cast(os.rdbuf())) + f = detail::get_file(*buf); +#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI + auto* rdbuf = os.rdbuf(); + if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf*>(rdbuf)) + f = sfbuf->file(); + else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf*>(rdbuf)) + f = fbuf->file(); +#endif +#ifdef _WIN32 + if (f) { + int fd = _fileno(f); + if (_isatty(fd)) { + os.flush(); + if (detail::write_console(fd, {buffer.data(), buffer.size()})) return; + } + } +#endif + detail::ignore_unused(f); + detail::write_buffer(os, buffer); +} + +/** + * Prints formatted data to the stream `os`. + * + * **Example**: + * + * fmt::print(cerr, "Don't {}!", "panic"); + */ +FMT_EXPORT template +void print(std::ostream& os, format_string fmt, T&&... args) { + fmt::vargs vargs = {{args...}}; + if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs); + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt.str, vargs); + detail::write_buffer(os, buffer); +} + +FMT_EXPORT template +void println(std::ostream& os, format_string fmt, T&&... args) { + fmt::print(os, FMT_STRING("{}\n"), + fmt::format(fmt, std::forward(args)...)); +} + +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/printf.h b/lib/spdlog/include/spdlog/fmt/bundled/printf.h new file mode 100644 index 0000000..cc066d8 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/printf.h @@ -0,0 +1,624 @@ +// Formatting library for C++ - legacy printf implementation +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#ifndef FMT_MODULE +# include // std::find +# include // std::numeric_limits +#endif + +#include "format.h" + +FMT_BEGIN_NAMESPACE +FMT_BEGIN_EXPORT + +template class basic_printf_context { + private: + basic_appender out_; + basic_format_args args_; + + static_assert(std::is_same::value || + std::is_same::value, + "Unsupported code unit type."); + + public: + using char_type = Char; + enum { builtin_types = 1 }; + + /// Constructs a `printf_context` object. References to the arguments are + /// stored in the context object so make sure they have appropriate lifetimes. + basic_printf_context(basic_appender out, + basic_format_args args) + : out_(out), args_(args) {} + + auto out() -> basic_appender { return out_; } + void advance_to(basic_appender) {} + + auto locale() -> locale_ref { return {}; } + + auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } +}; + +namespace detail { + +// Return the result via the out param to workaround gcc bug 77539. +template +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find(const char* first, const char* last, char value, + const char*& out) -> bool { + out = + static_cast(memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template struct int_checker { + template static auto fits_in_int(T value) -> bool { + return value <= to_unsigned(max_value()); + } + inline static auto fits_in_int(bool) -> bool { return true; } +}; + +template <> struct int_checker { + template static auto fits_in_int(T value) -> bool { + return value >= (std::numeric_limits::min)() && + value <= max_value(); + } + inline static auto fits_in_int(int) -> bool { return true; } +}; + +struct printf_precision_handler { + template ::value)> + auto operator()(T value) -> int { + if (!int_checker::is_signed>::fits_in_int(value)) + report_error("number is too big"); + return max_of(static_cast(value), 0); + } + + template ::value)> + auto operator()(T) -> int { + report_error("precision is not integer"); + return 0; + } +}; + +// An argument visitor that returns true iff arg is a zero integer. +struct is_zero_int { + template ::value)> + auto operator()(T value) -> bool { + return value == 0; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +template struct make_unsigned_or_bool : std::make_unsigned {}; + +template <> struct make_unsigned_or_bool { + using type = bool; +}; + +template class arg_converter { + private: + using char_type = typename Context::char_type; + + basic_format_arg& arg_; + char_type type_; + + public: + arg_converter(basic_format_arg& arg, char_type type) + : arg_(arg), type_(type) {} + + void operator()(bool value) { + if (type_ != 's') operator()(value); + } + + template ::value)> + void operator()(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using target_type = conditional_t::value, U, T>; + if (const_check(sizeof(target_type) <= sizeof(int))) { + // Extra casts are used to silence warnings. + using unsigned_type = typename make_unsigned_or_bool::type; + if (is_signed) + arg_ = static_cast(static_cast(value)); + else + arg_ = static_cast(static_cast(value)); + } else { + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + if (is_signed) + arg_ = static_cast(value); + else + arg_ = static_cast::type>(value); + } + } + + template ::value)> + void operator()(U) {} // No conversion needed for non-integral types. +}; + +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template +void convert_arg(basic_format_arg& arg, Char type) { + arg.visit(arg_converter(arg, type)); +} + +// Converts an integer argument to char for printf. +template class char_converter { + private: + basic_format_arg& arg_; + + public: + explicit char_converter(basic_format_arg& arg) : arg_(arg) {} + + template ::value)> + void operator()(T value) { + arg_ = static_cast(value); + } + + template ::value)> + 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 struct get_cstring { + template auto operator()(T) -> const Char* { return nullptr; } + auto operator()(const Char* s) -> const Char* { return s; } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class printf_width_handler { + private: + format_specs& specs_; + + public: + inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {} + + template ::value)> + auto operator()(T value) -> unsigned { + auto width = static_cast>(value); + if (detail::is_negative(value)) { + specs_.set_align(align::left); + width = 0 - width; + } + unsigned int_max = to_unsigned(max_value()); + if (width > int_max) report_error("number is too big"); + return static_cast(width); + } + + template ::value)> + auto operator()(T) -> unsigned { + report_error("width is not integer"); + return 0; + } +}; + +// Workaround for a bug with the XL compiler when initializing +// printf_arg_formatter's base class. +template +auto make_arg_formatter(basic_appender iter, format_specs& s) + -> arg_formatter { + return {iter, s, locale_ref()}; +} + +// The `printf` argument formatter. +template +class printf_arg_formatter : public arg_formatter { + private: + using base = arg_formatter; + using context_type = basic_printf_context; + + context_type& context_; + + void write_null_pointer(bool is_string = false) { + auto s = this->specs; + s.set_type(presentation_type::none); + write_bytes(this->out, is_string ? "(null)" : "(nil)", s); + } + + template void write(T value) { + detail::write(this->out, value, this->specs, this->locale); + } + + public: + printf_arg_formatter(basic_appender iter, format_specs& s, + context_type& ctx) + : base(make_arg_formatter(iter, s)), context_(ctx) {} + + void operator()(monostate value) { write(value); } + + template ::value)> + void operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and Char so use + // std::is_same instead. + if (!std::is_same::value) { + write(value); + return; + } + format_specs s = this->specs; + if (s.type() != presentation_type::none && + s.type() != presentation_type::chr) { + return (*this)(static_cast(value)); + } + s.set_sign(sign::none); + s.clear_alt(); + s.set_fill(' '); // Ignore '0' flag for char types. + // align::numeric needs to be overwritten here since the '0' flag is + // ignored for non-numeric types + if (s.align() == align::none || s.align() == align::numeric) + s.set_align(align::right); + detail::write(this->out, static_cast(value), s); + } + + template ::value)> + void operator()(T value) { + write(value); + } + + void operator()(const char* value) { + if (value) + write(value); + else + write_null_pointer(this->specs.type() != presentation_type::pointer); + } + + void operator()(const wchar_t* value) { + if (value) + write(value); + else + write_null_pointer(this->specs.type() != presentation_type::pointer); + } + + void operator()(basic_string_view value) { write(value); } + + void operator()(const void* value) { + if (value) + write(value); + else + write_null_pointer(); + } + + void operator()(typename basic_format_arg::handle handle) { + auto parse_ctx = parse_context({}); + handle.format(parse_ctx, context_); + } +}; + +template +void parse_flags(format_specs& specs, const Char*& it, const Char* end) { + for (; it != end; ++it) { + switch (*it) { + case '-': specs.set_align(align::left); break; + case '+': specs.set_sign(sign::plus); break; + case '0': specs.set_fill('0'); break; + case ' ': + if (specs.sign() != sign::plus) specs.set_sign(sign::space); + break; + case '#': specs.set_alt(); break; + default: return; + } + } +} + +template +auto parse_header(const Char*& it, const Char* end, format_specs& specs, + GetArg get_arg) -> int { + int arg_index = -1; + Char c = *it; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + int value = parse_nonnegative_int(it, end, -1); + if (it != end && *it == '$') { // value is an argument index + ++it; + arg_index = value != -1 ? value : max_value(); + } else { + if (c == '0') specs.set_fill('0'); + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + if (value == -1) report_error("number is too big"); + specs.width = value; + return arg_index; + } + } + } + parse_flags(specs, it, end); + // Parse width. + if (it != end) { + if (*it >= '0' && *it <= '9') { + specs.width = parse_nonnegative_int(it, end, -1); + if (specs.width == -1) report_error("number is too big"); + } else if (*it == '*') { + ++it; + specs.width = static_cast( + get_arg(-1).visit(detail::printf_width_handler(specs))); + } + } + return arg_index; +} + +inline auto parse_printf_presentation_type(char c, type t, bool& upper) + -> presentation_type { + using pt = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + switch (c) { + case 'd': return in(t, integral_set) ? pt::dec : pt::none; + case 'o': return in(t, integral_set) ? pt::oct : pt::none; + case 'X': upper = true; FMT_FALLTHROUGH; + case 'x': return in(t, integral_set) ? pt::hex : pt::none; + case 'E': upper = true; FMT_FALLTHROUGH; + case 'e': return in(t, float_set) ? pt::exp : pt::none; + case 'F': upper = true; FMT_FALLTHROUGH; + case 'f': return in(t, float_set) ? pt::fixed : pt::none; + case 'G': upper = true; FMT_FALLTHROUGH; + case 'g': return in(t, float_set) ? pt::general : pt::none; + case 'A': upper = true; FMT_FALLTHROUGH; + case 'a': return in(t, float_set) ? pt::hexfloat : pt::none; + case 'c': return in(t, integral_set) ? pt::chr : pt::none; + case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none; + case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none; + default: return pt::none; + } +} + +template +void vprintf(buffer& buf, basic_string_view format, + basic_format_args args) { + using iterator = basic_appender; + auto out = iterator(buf); + auto context = basic_printf_context(out, args); + auto parse_ctx = parse_context(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); + auto arg = context.arg(arg_index); + if (!arg) report_error("argument not found"); + return arg; + }; + + const Char* start = parse_ctx.begin(); + const Char* end = parse_ctx.end(); + auto it = start; + while (it != end) { + if (!find(it, end, '%', it)) { + it = end; // find leaves it == nullptr if it doesn't find '%'. + break; + } + Char c = *it++; + if (it != end && *it == c) { + write(out, basic_string_view(start, to_unsigned(it - start))); + start = ++it; + continue; + } + write(out, basic_string_view(start, to_unsigned(it - 1 - start))); + + auto specs = format_specs(); + specs.set_align(align::right); + + // Parse argument index, flags and width. + int arg_index = parse_header(it, end, specs, get_arg); + if (arg_index == 0) report_error("argument not found"); + + // Parse precision. + if (it != end && *it == '.') { + ++it; + c = it != end ? *it : 0; + if ('0' <= c && c <= '9') { + specs.precision = parse_nonnegative_int(it, end, 0); + } else if (c == '*') { + ++it; + specs.precision = + static_cast(get_arg(-1).visit(printf_precision_handler())); + } else { + specs.precision = 0; + } + } + + auto arg = get_arg(arg_index); + // For d, i, o, u, x, and X conversion specifiers, if a precision is + // specified, the '0' flag is ignored + if (specs.precision >= 0 && is_integral_type(arg.type())) { + // Ignore '0' for non-numeric types or if '-' present. + specs.set_fill(' '); + } + if (specs.precision >= 0 && arg.type() == type::cstring_type) { + auto str = arg.visit(get_cstring()); + auto str_end = str + specs.precision; + auto nul = std::find(str, str_end, Char()); + auto sv = basic_string_view( + str, to_unsigned(nul != str_end ? nul - str : specs.precision)); + arg = sv; + } + if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt(); + if (specs.fill_unit() == '0') { + if (is_arithmetic_type(arg.type()) && specs.align() != align::left) { + specs.set_align(align::numeric); + } else { + // Ignore '0' flag for non-numeric types or if '-' flag is also present. + specs.set_fill(' '); + } + } + + // Parse length and convert the argument to the required type. + c = it != end ? *it++ : 0; + Char t = it != end ? *it : 0; + switch (c) { + case 'h': + if (t == 'h') { + ++it; + t = it != end ? *it : 0; + convert_arg(arg, t); + } else { + convert_arg(arg, t); + } + break; + case 'l': + if (t == 'l') { + ++it; + t = it != end ? *it : 0; + convert_arg(arg, t); + } else { + convert_arg(arg, t); + } + break; + case 'j': convert_arg(arg, t); break; + case 'z': convert_arg(arg, t); break; + case 't': convert_arg(arg, t); break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: --it; convert_arg(arg, c); + } + + // Parse type. + if (it == end) report_error("invalid format string"); + char type = static_cast(*it++); + if (is_integral_type(arg.type())) { + // Normalize type. + switch (type) { + case 'i': + case 'u': type = 'd'; break; + case 'c': + arg.visit(char_converter>(arg)); + break; + } + } + bool upper = false; + specs.set_type(parse_printf_presentation_type(type, arg.type(), upper)); + if (specs.type() == presentation_type::none) + report_error("invalid format specifier"); + if (upper) specs.set_upper(); + + start = it; + + // Format argument. + arg.visit(printf_arg_formatter(out, specs, context)); + } + write(out, basic_string_view(start, to_unsigned(it - start))); +} +} // namespace detail + +using printf_context = basic_printf_context; +using wprintf_context = basic_printf_context; + +using printf_args = basic_format_args; +using wprintf_args = basic_format_args; + +/// Constructs an `format_arg_store` object that contains references to +/// arguments and can be implicitly converted to `printf_args`. +template +inline auto make_printf_args(T&... args) + -> decltype(fmt::make_format_args>(args...)) { + return fmt::make_format_args>(args...); +} + +template struct vprintf_args { + using type = basic_format_args>; +}; + +template +inline auto vsprintf(basic_string_view fmt, + typename vprintf_args::type args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vprintf(buf, fmt, args); + return {buf.data(), buf.size()}; +} + +/** + * Formats `args` according to specifications in `fmt` and returns the result + * as as string. + * + * **Example**: + * + * std::string message = fmt::sprintf("The answer is %d", 42); + */ +template +inline auto sprintf(string_view fmt, const T&... args) -> std::string { + return vsprintf(fmt, make_printf_args(args...)); +} +template +FMT_DEPRECATED auto sprintf(basic_string_view fmt, const T&... args) + -> std::wstring { + return vsprintf(fmt, make_printf_args(args...)); +} + +template +auto vfprintf(std::FILE* f, basic_string_view fmt, + typename vprintf_args::type args) -> int { + auto buf = basic_memory_buffer(); + detail::vprintf(buf, fmt, args); + size_t size = buf.size(); + return std::fwrite(buf.data(), sizeof(Char), size, f) < size + ? -1 + : static_cast(size); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `f`. + * + * **Example**: + * + * fmt::fprintf(stderr, "Don't %s!", "panic"); + */ +template +inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int { + return vfprintf(f, fmt, make_printf_args(args...)); +} +template +FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view fmt, + const T&... args) -> int { + return vfprintf(f, fmt, make_printf_args(args...)); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::printf("Elapsed time: %.2f seconds", 1.23); + */ +template +inline auto printf(string_view fmt, const T&... args) -> int { + return vfprintf(stdout, fmt, make_printf_args(args...)); +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_PRINTF_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/ranges.h b/lib/spdlog/include/spdlog/fmt/bundled/ranges.h new file mode 100644 index 0000000..36b38e2 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/ranges.h @@ -0,0 +1,851 @@ +// Formatting library for C++ - range and tuple support +// +// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include +#endif + +#include "format.h" + +#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound) +# define FMT_LIFETIMEBOUND [[clang::lifetimebound]] +#else +# define FMT_LIFETIMEBOUND +#endif +FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address") + +FMT_BEGIN_NAMESPACE + +FMT_EXPORT +enum class range_format { disabled, map, set, sequence, string, debug_string }; + +namespace detail { + +template class is_map { + template static auto check(U*) -> typename U::mapped_type; + template static void check(...); + + public: + static constexpr bool value = + !std::is_void(nullptr))>::value; +}; + +template class is_set { + template static auto check(U*) -> typename U::key_type; + template static void check(...); + + public: + static constexpr bool value = + !std::is_void(nullptr))>::value && !is_map::value; +}; + +// C array overload +template +auto range_begin(const T (&arr)[N]) -> const T* { + return arr; +} +template auto range_end(const T (&arr)[N]) -> const T* { + return arr + N; +} + +template +struct has_member_fn_begin_end_t : std::false_type {}; + +template +struct has_member_fn_begin_end_t().begin()), + decltype(std::declval().end())>> + : std::true_type {}; + +// Member function overloads. +template +auto range_begin(T&& rng) -> decltype(static_cast(rng).begin()) { + return static_cast(rng).begin(); +} +template +auto range_end(T&& rng) -> decltype(static_cast(rng).end()) { + return static_cast(rng).end(); +} + +// ADL overloads. Only participate in overload resolution if member functions +// are not found. +template +auto range_begin(T&& rng) + -> enable_if_t::value, + decltype(begin(static_cast(rng)))> { + return begin(static_cast(rng)); +} +template +auto range_end(T&& rng) -> enable_if_t::value, + decltype(end(static_cast(rng)))> { + return end(static_cast(rng)); +} + +template +struct has_const_begin_end : std::false_type {}; +template +struct has_mutable_begin_end : std::false_type {}; + +template +struct has_const_begin_end< + T, void_t&>())), + decltype(detail::range_end( + std::declval&>()))>> + : std::true_type {}; + +template +struct has_mutable_begin_end< + T, void_t())), + decltype(detail::range_end(std::declval())), + // the extra int here is because older versions of MSVC don't + // SFINAE properly unless there are distinct types + int>> : std::true_type {}; + +template struct is_range_ : std::false_type {}; +template +struct is_range_ + : std::integral_constant::value || + has_mutable_begin_end::value)> {}; + +// tuple_size and tuple_element check. +template class is_tuple_like_ { + template ::type> + static auto check(U* p) -> decltype(std::tuple_size::value, 0); + template static void check(...); + + public: + static constexpr bool value = + !std::is_void(nullptr))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900 +template +using integer_sequence = std::integer_sequence; +template using index_sequence = std::index_sequence; +template using make_index_sequence = std::make_index_sequence; +#else +template struct integer_sequence { + using value_type = T; + + static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); } +}; + +template using index_sequence = integer_sequence; + +template +struct make_integer_sequence : make_integer_sequence {}; +template +struct make_integer_sequence : integer_sequence {}; + +template +using make_index_sequence = make_integer_sequence; +#endif + +template +using tuple_index_sequence = make_index_sequence::value>; + +template ::value> +class is_tuple_formattable_ { + public: + static constexpr bool value = false; +}; +template class is_tuple_formattable_ { + template + static auto all_true(index_sequence, + integer_sequence= 0)...>) -> std::true_type; + static auto all_true(...) -> std::false_type; + + template + static auto check(index_sequence) -> decltype(all_true( + index_sequence{}, + integer_sequence::type, + C>::value)...>{})); + + public: + static constexpr bool value = + decltype(check(tuple_index_sequence{}))::value; +}; + +template +FMT_CONSTEXPR void for_each(index_sequence, Tuple&& t, F&& f) { + using std::get; + // Using a free function get(Tuple) now. + const int unused[] = {0, ((void)f(get(t)), 0)...}; + ignore_unused(unused); +} + +template +FMT_CONSTEXPR void for_each(Tuple&& t, F&& f) { + for_each(tuple_index_sequence>(), + std::forward(t), std::forward(f)); +} + +template +void for_each2(index_sequence, Tuple1&& t1, Tuple2&& t2, F&& f) { + using std::get; + const int unused[] = {0, ((void)f(get(t1), get(t2)), 0)...}; + ignore_unused(unused); +} + +template +void for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) { + for_each2(tuple_index_sequence>(), + std::forward(t1), std::forward(t2), + std::forward(f)); +} + +namespace tuple { +// Workaround a bug in MSVC 2019 (v140). +template +using result_t = std::tuple, Char>...>; + +using std::get; +template +auto get_formatters(index_sequence) + -> result_t(std::declval()))...>; +} // namespace tuple + +#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 +// Older MSVC doesn't get the reference type correctly for arrays. +template struct range_reference_type_impl { + using type = decltype(*detail::range_begin(std::declval())); +}; + +template struct range_reference_type_impl { + using type = T&; +}; + +template +using range_reference_type = typename range_reference_type_impl::type; +#else +template +using range_reference_type = + decltype(*detail::range_begin(std::declval())); +#endif + +// We don't use the Range's value_type for anything, but we do need the Range's +// reference type, with cv-ref stripped. +template +using uncvref_type = remove_cvref_t>; + +template +struct range_format_kind_ + : std::integral_constant, T>::value + ? range_format::disabled + : is_map::value ? range_format::map + : is_set::value ? range_format::set + : range_format::sequence> {}; + +template +using range_format_constant = std::integral_constant; + +// These are not generic lambdas for compatibility with C++11. +template struct parse_empty_specs { + template FMT_CONSTEXPR void operator()(Formatter& f) { + f.parse(ctx); + detail::maybe_set_debug_format(f, true); + } + parse_context& ctx; +}; +template struct format_tuple_element { + using char_type = typename FormatContext::char_type; + + template + void operator()(const formatter& f, const T& v) { + if (i > 0) ctx.advance_to(detail::copy(separator, ctx.out())); + ctx.advance_to(f.format(v, ctx)); + ++i; + } + + int i; + FormatContext& ctx; + basic_string_view separator; +}; + +} // namespace detail + +FMT_EXPORT +template struct is_tuple_like { + static constexpr bool value = + detail::is_tuple_like_::value && !detail::is_range_::value; +}; + +FMT_EXPORT +template struct is_tuple_formattable { + static constexpr bool value = detail::is_tuple_formattable_::value; +}; + +template +struct formatter::value && + fmt::is_tuple_formattable::value>> { + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end && detail::to_ascii(*it) == 'n') { + ++it; + set_brackets({}, {}); + set_separator({}); + } + if (it != end && *it != '}') report_error("invalid format specifier"); + ctx.advance_to(it); + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(const Tuple& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + ctx.advance_to(detail::copy(opening_bracket_, ctx.out())); + detail::for_each2( + formatters_, value, + detail::format_tuple_element{0, ctx, separator_}); + return detail::copy(closing_bracket_, ctx.out()); + } +}; + +FMT_EXPORT +template struct is_range { + static constexpr bool value = + detail::is_range_::value && !detail::has_to_string_view::value; +}; + +namespace detail { + +template +using range_formatter_type = formatter, Char>; + +template +using maybe_const_range = + conditional_t::value, const R, R>; + +template +struct is_formattable_delayed + : is_formattable>, Char> {}; +} // namespace detail + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + +FMT_EXPORT +template +struct range_formatter; + +template +struct range_formatter< + T, Char, + enable_if_t>, + is_formattable>::value>> { + private: + detail::range_formatter_type underlying_; + basic_string_view separator_ = detail::string_literal{}; + basic_string_view opening_bracket_ = + detail::string_literal{}; + basic_string_view closing_bracket_ = + detail::string_literal{}; + bool is_debug = false; + + template ::value)> + auto write_debug_string(Output& out, It it, Sentinel end) const -> Output { + auto buf = basic_memory_buffer(); + for (; it != end; ++it) buf.push_back(*it); + auto specs = format_specs(); + specs.set_type(presentation_type::debug); + return detail::write( + out, basic_string_view(buf.data(), buf.size()), specs); + } + + template ::value)> + auto write_debug_string(Output& out, It, Sentinel) const -> Output { + return out; + } + + public: + FMT_CONSTEXPR range_formatter() {} + + FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type& { + return underlying_; + } + + FMT_CONSTEXPR void set_separator(basic_string_view sep) { + separator_ = sep; + } + + FMT_CONSTEXPR void set_brackets(basic_string_view open, + basic_string_view close) { + opening_bracket_ = open; + closing_bracket_ = close; + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + detail::maybe_set_debug_format(underlying_, true); + if (it == end) return underlying_.parse(ctx); + + switch (detail::to_ascii(*it)) { + case 'n': + set_brackets({}, {}); + ++it; + break; + case '?': + is_debug = true; + set_brackets({}, {}); + ++it; + if (it == end || *it != 's') report_error("invalid format specifier"); + FMT_FALLTHROUGH; + case 's': + if (!std::is_same::value) + report_error("invalid format specifier"); + if (!is_debug) { + set_brackets(detail::string_literal{}, + detail::string_literal{}); + set_separator({}); + detail::maybe_set_debug_format(underlying_, false); + } + ++it; + return it; + } + + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + detail::maybe_set_debug_format(underlying_, false); + ++it; + } + + ctx.advance_to(it); + return underlying_.parse(ctx); + } + + template + auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + auto it = detail::range_begin(range); + auto end = detail::range_end(range); + if (is_debug) return write_debug_string(out, std::move(it), end); + + out = detail::copy(opening_bracket_, out); + int i = 0; + for (; it != end; ++it) { + if (i > 0) out = detail::copy(separator_, out); + ctx.advance_to(out); + auto&& item = *it; // Need an lvalue + out = underlying_.format(item, ctx); + ++i; + } + out = detail::copy(closing_bracket_, out); + return out; + } +}; + +FMT_EXPORT +template +struct range_format_kind + : conditional_t< + is_range::value, detail::range_format_kind_, + std::integral_constant> {}; + +template +struct formatter< + R, Char, + enable_if_t::value != range_format::disabled && + range_format_kind::value != range_format::map && + range_format_kind::value != range_format::string && + range_format_kind::value != range_format::debug_string>, + detail::is_formattable_delayed>::value>> { + private: + using range_type = detail::maybe_const_range; + range_formatter, Char> range_formatter_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR formatter() { + if (detail::const_check(range_format_kind::value != + range_format::set)) + return; + range_formatter_.set_brackets(detail::string_literal{}, + detail::string_literal{}); + } + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return range_formatter_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + return range_formatter_.format(range, ctx); + } +}; + +// A map formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::map>, + detail::is_formattable_delayed>::value>> { + private: + using map_type = detail::maybe_const_range; + using element_type = detail::uncvref_type; + + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + bool no_delimiters_ = false; + + public: + FMT_CONSTEXPR formatter() {} + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end) { + if (detail::to_ascii(*it) == 'n') { + no_delimiters_ = true; + ++it; + } + if (it != end && *it != '}') { + if (*it != ':') report_error("invalid format specifier"); + ++it; + } + ctx.advance_to(it); + } + detail::for_each(formatters_, detail::parse_empty_specs{ctx}); + return it; + } + + template + auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) { + auto out = ctx.out(); + basic_string_view open = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(open, out); + int i = 0; + basic_string_view sep = detail::string_literal{}; + for (auto&& value : map) { + if (i > 0) out = detail::copy(sep, out); + ctx.advance_to(out); + detail::for_each2(formatters_, value, + detail::format_tuple_element{ + 0, ctx, detail::string_literal{}}); + ++i; + } + basic_string_view close = detail::string_literal{}; + if (!no_delimiters_) out = detail::copy(close, out); + return out; + } +}; + +// A (debug_)string formatter. +template +struct formatter< + R, Char, + enable_if_t::value == range_format::string || + range_format_kind::value == + range_format::debug_string>> { + private: + using range_type = detail::maybe_const_range; + using string_type = + conditional_t, + decltype(detail::range_begin(std::declval())), + decltype(detail::range_end(std::declval()))>::value, + detail::std_string_view, std::basic_string>; + + formatter underlying_; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return underlying_.parse(ctx); + } + + template + auto format(range_type& range, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + out = underlying_.format( + string_type{detail::range_begin(range), detail::range_end(range)}, ctx); + if (detail::const_check(range_format_kind::value == + range_format::debug_string)) + *out++ = '"'; + return out; + } +}; + +template +struct join_view : detail::view { + It begin; + Sentinel end; + basic_string_view sep; + + join_view(It b, Sentinel e, basic_string_view s) + : begin(std::move(b)), end(e), sep(s) {} +}; + +template +struct formatter, Char> { + private: + using value_type = +#ifdef __cpp_lib_ranges + std::iter_value_t; +#else + typename std::iterator_traits::value_type; +#endif + formatter, Char> value_formatter_; + + using view = conditional_t::value, + const join_view, + join_view>; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return value_formatter_.parse(ctx); + } + + template + auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { + using iter = + conditional_t::value, It, It&>; + iter it = value.begin; + auto out = ctx.out(); + if (it == value.end) return out; + out = value_formatter_.format(*it, ctx); + ++it; + while (it != value.end) { + out = detail::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = value_formatter_.format(*it, ctx); + ++it; + } + return out; + } +}; + +FMT_EXPORT +template struct tuple_join_view : detail::view { + const Tuple& tuple; + basic_string_view sep; + + tuple_join_view(const Tuple& t, basic_string_view s) + : tuple(t), sep{s} {} +}; + +// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers +// support in tuple_join. It is disabled by default because of issues with +// the dynamic width and precision. +#ifndef FMT_TUPLE_JOIN_SPECIFIERS +# define FMT_TUPLE_JOIN_SPECIFIERS 0 +#endif + +template +struct formatter, Char, + enable_if_t::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx, std::tuple_size()); + } + + template + auto format(const tuple_join_view& value, + FormatContext& ctx) const -> typename FormatContext::iterator { + return do_format(value, ctx, std::tuple_size()); + } + + private: + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; + + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + return ctx.begin(); + } + + template + FMT_CONSTEXPR auto do_parse(parse_context& ctx, + std::integral_constant) + -> const Char* { + auto end = ctx.begin(); +#if FMT_TUPLE_JOIN_SPECIFIERS + end = std::get::value - N>(formatters_).parse(ctx); + if (N > 1) { + auto end1 = do_parse(ctx, std::integral_constant()); + if (end != end1) + report_error("incompatible format specs for tuple elements"); + } +#endif + return end; + } + + template + auto do_format(const tuple_join_view&, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + return ctx.out(); + } + + template + auto do_format(const tuple_join_view& value, FormatContext& ctx, + std::integral_constant) const -> + typename FormatContext::iterator { + using std::get; + auto out = + std::get::value - N>(formatters_) + .format(get::value - N>(value.tuple), ctx); + if (N <= 1) return out; + out = detail::copy(value.sep, out); + ctx.advance_to(out); + return do_format(value, ctx, std::integral_constant()); + } +}; + +namespace detail { +// Check if T has an interface like a container adaptor (e.g. std::stack, +// std::queue, std::priority_queue). +template class is_container_adaptor_like { + template static auto check(U* p) -> typename U::container_type; + template static void check(...); + + public: + static constexpr bool value = + !std::is_void(nullptr))>::value; +}; + +template struct all { + const Container& c; + auto begin() const -> typename Container::const_iterator { return c.begin(); } + auto end() const -> typename Container::const_iterator { return c.end(); } +}; +} // namespace detail + +template +struct formatter< + T, Char, + enable_if_t, + bool_constant::value == + range_format::disabled>>::value>> + : formatter, Char> { + using all = detail::all; + template + auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { + struct getter : T { + static auto get(const T& v) -> all { + return {v.*(&getter::c)}; // Access c through the derived class. + } + }; + return formatter::format(getter::get(value), ctx); + } +}; + +FMT_BEGIN_EXPORT + +/// Returns a view that formats the iterator range `[begin, end)` with elements +/// separated by `sep`. +template +auto join(It begin, Sentinel end, string_view sep) -> join_view { + return {std::move(begin), end, sep}; +} + +/** + * Returns a view that formats `range` with elements separated by `sep`. + * + * **Example**: + * + * auto v = std::vector{1, 2, 3}; + * fmt::print("{}", fmt::join(v, ", ")); + * // Output: 1, 2, 3 + * + * `fmt::join` applies passed format specifiers to the range elements: + * + * fmt::print("{:02}", fmt::join(v, ", ")); + * // Output: 01, 02, 03 + */ +template ::value)> +auto join(Range&& r, string_view sep) + -> join_view { + return {detail::range_begin(r), detail::range_end(r), sep}; +} + +/** + * Returns an object that formats `std::tuple` with elements separated by `sep`. + * + * **Example**: + * + * auto t = std::tuple(1, 'a'); + * fmt::print("{}", fmt::join(t, ", ")); + * // Output: 1, a + */ +template ::value)> +FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +/** + * Returns an object that formats `std::initializer_list` with elements + * separated by `sep`. + * + * **Example**: + * + * fmt::print("{}", fmt::join({1, 2, 3}, ", ")); + * // Output: "1, 2, 3" + */ +template +auto join(std::initializer_list list, string_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/std.h b/lib/spdlog/include/spdlog/fmt/bundled/std.h new file mode 100644 index 0000000..184c6d2 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/std.h @@ -0,0 +1,727 @@ +// Formatting library for C++ - formatters for standard library types +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_STD_H_ +#define FMT_STD_H_ + +#include "format.h" +#include "ostream.h" + +#ifndef FMT_MODULE +# include +# include +# include +# include +# include // std::reference_wrapper +# include +# include +# include +# include // std::type_info +# include // std::make_index_sequence + +// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. +# if FMT_CPLUSPLUS >= 201703L +# if FMT_HAS_INCLUDE() && \ + (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0) +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# if FMT_HAS_INCLUDE() +# include +# endif +# endif +// Use > instead of >= in the version check because may be +// available after C++17 but before C++20 is marked as implemented. +# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE() +# include +# endif +# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE() +# include +# endif +#endif // FMT_MODULE + +#if FMT_HAS_INCLUDE() +# include +#endif + +// GCC 4 does not support FMT_HAS_INCLUDE. +#if FMT_HAS_INCLUDE() || defined(__GLIBCXX__) +# include +// Android NDK with gabi++ library on some architectures does not implement +// abi::__cxa_demangle(). +# ifndef __GABIXX_CXXABI_H__ +# define FMT_HAS_ABI_CXA_DEMANGLE +# endif +#endif + +#ifdef FMT_CPP_LIB_FILESYSTEM +// Use the provided definition. +#elif defined(__cpp_lib_filesystem) +# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem +#else +# define FMT_CPP_LIB_FILESYSTEM 0 +#endif + +#ifdef FMT_CPP_LIB_VARIANT +// Use the provided definition. +#elif defined(__cpp_lib_variant) +# define FMT_CPP_LIB_VARIANT __cpp_lib_variant +#else +# define FMT_CPP_LIB_VARIANT 0 +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +#if FMT_CPP_LIB_FILESYSTEM + +template +auto get_path_string(const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && std::is_same_v) + return to_utf8(native, to_utf8_error_policy::replace); + else + return p.string(); +} + +template +void write_escaped_path(basic_memory_buffer& quoted, + const std::filesystem::path& p, + const std::basic_string& native) { + if constexpr (std::is_same_v && + std::is_same_v) { + auto buf = basic_memory_buffer(); + write_escaped_string(std::back_inserter(buf), native); + bool valid = to_utf8::convert(quoted, {buf.data(), buf.size()}); + FMT_ASSERT(valid, "invalid utf16"); + } else if constexpr (std::is_same_v) { + write_escaped_string( + std::back_inserter(quoted), native); + } else { + write_escaped_string(std::back_inserter(quoted), p.string()); + } +} + +#endif // FMT_CPP_LIB_FILESYSTEM + +#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT + +template +auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx) + -> OutputIt { + if constexpr (has_to_string_view::value) + return write_escaped_string(out, detail::to_string_view(v)); + if constexpr (std::is_same_v) return write_escaped_char(out, v); + + formatter, Char> underlying; + maybe_set_debug_format(underlying, true); + return underlying.format(v, ctx); +} +#endif + +#if FMT_CPP_LIB_VARIANT + +template struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_> : std::true_type {}; + +template class is_variant_formattable { + template + static auto check(std::index_sequence) -> std::conjunction< + is_formattable, Char>...>; + + public: + static constexpr bool value = decltype(check( + std::make_index_sequence::value>()))::value; +}; + +#endif // FMT_CPP_LIB_VARIANT + +#if FMT_USE_RTTI +inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view, + char* begin) -> string_view { + // Normalization of stdlib inline namespace names. + // libc++ inline namespaces. + // std::__1::* -> std::* + // std::__1::__fs::* -> std::* + // libstdc++ inline namespaces. + // std::__cxx11::* -> std::* + // std::filesystem::__cxx11::* -> std::filesystem::* + if (demangled_name_view.starts_with("std::")) { + char* to = begin + 5; // std:: + for (const char *from = to, *end = begin + demangled_name_view.size(); + from < end;) { + // This is safe, because demangled_name is NUL-terminated. + if (from[0] == '_' && from[1] == '_') { + const char* next = from + 1; + while (next < end && *next != ':') next++; + if (next[0] == ':' && next[1] == ':') { + from = next + 2; + continue; + } + } + *to++ = *from++; + } + demangled_name_view = {begin, detail::to_unsigned(to - begin)}; + } + return demangled_name_view; +} + +template +auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out) + -> OutputIt { + const string_view demangled_name(abi_name_view); + for (size_t i = 0; i < demangled_name.size(); ++i) { + auto sub = demangled_name; + sub.remove_prefix(i); + if (sub.starts_with("enum ")) { + i += 4; + continue; + } + if (sub.starts_with("class ") || sub.starts_with("union ")) { + i += 5; + continue; + } + if (sub.starts_with("struct ")) { + i += 6; + continue; + } + if (*sub.begin() != ' ') *out++ = *sub.begin(); + } + return out; +} + +template +auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { +# ifdef FMT_HAS_ABI_CXA_DEMANGLE + int status = 0; + size_t size = 0; + std::unique_ptr demangled_name_ptr( + abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free); + + string_view demangled_name_view; + if (demangled_name_ptr) { + demangled_name_view = normalize_libcxx_inline_namespaces( + demangled_name_ptr.get(), demangled_name_ptr.get()); + } else { + demangled_name_view = string_view(ti.name()); + } + return detail::write_bytes(out, demangled_name_view); +# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE) + return normalize_msvc_abi_name(ti.name(), out); +# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION) + const string_view demangled_name = ti.name(); + std::string name_copy(demangled_name.size(), '\0'); + // normalize_msvc_abi_name removes class, struct, union etc that MSVC has in + // front of types + name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()), + name_copy.end()); + // normalize_libcxx_inline_namespaces removes the inline __1, __2, etc + // namespaces libc++ uses for ABI versioning On MSVC ABI + libc++ + // environments, we need to eliminate both of them. + const string_view normalized_name = + normalize_libcxx_inline_namespaces(name_copy, name_copy.data()); + return detail::write_bytes(out, normalized_name); +# else + return detail::write_bytes(out, string_view(ti.name())); +# endif +} + +#endif // FMT_USE_RTTI + +template +struct has_flip : std::false_type {}; + +template +struct has_flip().flip())>> + : std::true_type {}; + +template struct is_bit_reference_like { + static constexpr bool value = std::is_convertible::value && + std::is_nothrow_assignable::value && + has_flip::value; +}; + +// Workaround for libc++ incompatibility with C++ standard. +// According to the Standard, `bitset::operator[] const` returns bool. +#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD) +template +struct is_bit_reference_like> { + static constexpr bool value = true; +}; +#endif + +template +struct has_format_as : std::false_type {}; +template +struct has_format_as()))>> + : std::true_type {}; + +template +struct has_format_as_member : std::false_type {}; +template +struct has_format_as_member< + T, void_t::format_as(std::declval()))>> + : std::true_type {}; + +} // namespace detail + +template +auto ptr(const std::unique_ptr& p) -> const void* { + return p.get(); +} +template auto ptr(const std::shared_ptr& p) -> const void* { + return p.get(); +} + +#if FMT_CPP_LIB_FILESYSTEM + +template struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + bool debug_ = false; + char path_type_ = 0; + + public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + + FMT_CONSTEXPR auto parse(parse_context& ctx) { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + if (it == end) return it; + + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++); + return it; + } + + template + auto format(const std::filesystem::path& p, FormatContext& ctx) const { + auto specs = specs_; + auto path_string = + !path_type_ ? p.native() + : p.generic_string(); + + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + if (!debug_) { + auto s = detail::get_path_string(p, path_string); + return detail::write(ctx.out(), basic_string_view(s), specs); + } + auto quoted = basic_memory_buffer(); + detail::write_escaped_path(quoted, p, path_string); + return detail::write(ctx.out(), + basic_string_view(quoted.data(), quoted.size()), + specs); + } +}; + +class path : public std::filesystem::path { + public: + auto display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{}"), base); + } + auto system_string() const -> std::string { return string(); } + + auto generic_display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{:g}"), base); + } + auto generic_system_string() const -> std::string { return generic_string(); } +}; + +#endif // FMT_CPP_LIB_FILESYSTEM + +template +struct formatter, Char> + : nested_formatter, Char> { + private: + // This is a functor because C++11 doesn't support generic lambdas. + struct writer { + const std::bitset& bs; + + template + FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { + for (auto pos = N; pos > 0; --pos) + out = detail::write(out, bs[pos - 1] ? Char('1') : Char('0')); + return out; + } + }; + + public: + template + auto format(const std::bitset& bs, FormatContext& ctx) const + -> decltype(ctx.out()) { + return this->write_padded(ctx, writer{bs}); + } +}; + +template +struct formatter : basic_ostream_formatter {}; + +#ifdef __cpp_lib_optional +template +struct formatter, Char, + std::enable_if_t::value>> { + private: + formatter, Char> underlying_; + static constexpr basic_string_view optional = + detail::string_literal{}; + static constexpr basic_string_view none = + detail::string_literal{}; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) { + detail::maybe_set_debug_format(underlying_, true); + return underlying_.parse(ctx); + } + + template + auto format(const std::optional& opt, FormatContext& ctx) const + -> decltype(ctx.out()) { + if (!opt) return detail::write(ctx.out(), none); + + auto out = ctx.out(); + out = detail::write(out, optional); + ctx.advance_to(out); + out = underlying_.format(*opt, ctx); + return detail::write(out, ')'); + } +}; +#endif // __cpp_lib_optional + +#ifdef __cpp_lib_expected +template +struct formatter, Char, + std::enable_if_t<(std::is_void::value || + is_formattable::value) && + is_formattable::value>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::expected& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + if (value.has_value()) { + out = detail::write(out, "expected("); + if constexpr (!std::is_void::value) + out = detail::write_escaped_alternative(out, *value, ctx); + } else { + out = detail::write(out, "unexpected("); + out = detail::write_escaped_alternative(out, value.error(), ctx); + } + *out++ = ')'; + return out; + } +}; +#endif // __cpp_lib_expected + +#ifdef __cpp_lib_source_location +template <> struct formatter { + FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } + + template + auto format(const std::source_location& loc, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write(out, loc.file_name()); + out = detail::write(out, ':'); + out = detail::write(out, loc.line()); + out = detail::write(out, ':'); + out = detail::write(out, loc.column()); + out = detail::write(out, ": "); + out = detail::write(out, loc.function_name()); + return out; + } +}; +#endif + +#if FMT_CPP_LIB_VARIANT + +template struct is_variant_like { + static constexpr bool value = detail::is_variant_like_::value; +}; + +template struct formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const std::monostate&, FormatContext& ctx) const + -> decltype(ctx.out()) { + return detail::write(ctx.out(), "monostate"); + } +}; + +template +struct formatter, + detail::is_variant_formattable>>> { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return ctx.begin(); + } + + template + auto format(const Variant& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + out = detail::write(out, "variant("); + FMT_TRY { + std::visit( + [&](const auto& v) { + out = detail::write_escaped_alternative(out, v, ctx); + }, + value); + } + FMT_CATCH(const std::bad_variant_access&) { + detail::write(out, "valueless by exception"); + } + *out++ = ')'; + return out; + } +}; + +#endif // FMT_CPP_LIB_VARIANT + +template <> struct formatter { + private: + format_specs specs_; + detail::arg_ref width_ref_; + bool debug_ = false; + + public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + auto it = ctx.begin(), end = ctx.end(); + if (it == end) return it; + + it = detail::parse_align(it, end, specs_); + + char c = *it; + if (it != end && ((c >= '0' && c <= '9') || c == '{')) + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + if (it != end && *it == 's') { + specs_.set_type(presentation_type::string); + ++it; + } + return it; + } + + template + FMT_CONSTEXPR20 auto format(const std::error_code& ec, + FormatContext& ctx) const -> decltype(ctx.out()) { + auto specs = specs_; + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + auto buf = memory_buffer(); + if (specs_.type() == presentation_type::string) { + buf.append(ec.message()); + } else { + buf.append(string_view(ec.category().name())); + buf.push_back(':'); + detail::write(appender(buf), ec.value()); + } + auto quoted = memory_buffer(); + auto str = string_view(buf.data(), buf.size()); + if (debug_) { + detail::write_escaped_string(std::back_inserter(quoted), str); + str = string_view(quoted.data(), quoted.size()); + } + return detail::write(ctx.out(), str, specs); + } +}; + +#if FMT_USE_RTTI +template <> struct formatter { + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + return ctx.begin(); + } + + template + auto format(const std::type_info& ti, Context& ctx) const + -> decltype(ctx.out()) { + return detail::write_demangled_name(ctx.out(), ti); + } +}; +#endif // FMT_USE_RTTI + +template +struct formatter< + T, char, + typename std::enable_if::value>::type> { + private: + bool with_typename_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it == end || *it == '}') return it; + if (*it == 't') { + ++it; + with_typename_ = FMT_USE_RTTI != 0; + } + return it; + } + + template + auto format(const std::exception& ex, Context& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); +#if FMT_USE_RTTI + if (with_typename_) { + out = detail::write_demangled_name(out, typeid(ex)); + *out++ = ':'; + *out++ = ' '; + } +#endif + return detail::write_bytes(out, string_view(ex.what())); + } +}; + +// We can't use std::vector::reference and +// std::bitset::reference because the compiler can't deduce Allocator and N +// in partial specialization. +template +struct formatter::value>> + : formatter { + template + FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v, ctx); + } +}; + +template +struct formatter, Char, + enable_if_t::value>> + : formatter { + template + auto format(const std::atomic& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.load(), ctx); + } +}; + +#ifdef __cpp_lib_atomic_flag_test +template +struct formatter : formatter { + template + auto format(const std::atomic_flag& v, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter::format(v.test(), ctx); + } +}; +#endif // __cpp_lib_atomic_flag_test + +template struct formatter, Char> { + private: + detail::dynamic_format_specs specs_; + + template + FMT_CONSTEXPR auto do_format(const std::complex& c, + detail::dynamic_format_specs& specs, + FormatContext& ctx, OutputIt out) const + -> OutputIt { + if (c.real() != 0) { + *out++ = Char('('); + out = detail::write(out, c.real(), specs, ctx.locale()); + specs.set_sign(sign::plus); + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + *out++ = Char(')'); + return out; + } + out = detail::write(out, c.imag(), specs, ctx.locale()); + if (!detail::isfinite(c.imag())) *out++ = Char(' '); + *out++ = Char('i'); + return out; + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, + detail::type_constant::value); + } + + template + auto format(const std::complex& c, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto specs = specs_; + if (specs.dynamic()) { + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); + } + + if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); + auto buf = basic_memory_buffer(); + + auto outer_specs = format_specs(); + outer_specs.width = specs.width; + outer_specs.copy_fill_from(specs); + outer_specs.set_align(specs.align()); + + specs.width = 0; + specs.set_fill({}); + specs.set_align(align::none); + + do_format(c, specs, ctx, basic_appender(buf)); + return detail::write(ctx.out(), + basic_string_view(buf.data(), buf.size()), + outer_specs); + } +}; + +template +struct formatter, Char, + // Guard against format_as because reference_wrapper is + // implicitly convertible to T&. + enable_if_t, Char>::value && + !detail::has_format_as::value && + !detail::has_format_as_member::value>> + : formatter, Char> { + template + auto format(std::reference_wrapper ref, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format(ref.get(), ctx); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_STD_H_ diff --git a/lib/spdlog/include/spdlog/fmt/bundled/xchar.h b/lib/spdlog/include/spdlog/fmt/bundled/xchar.h new file mode 100644 index 0000000..9334b87 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/bundled/xchar.h @@ -0,0 +1,356 @@ +// 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_XCHAR_H_ +#define FMT_XCHAR_H_ + +#include "color.h" +#include "format.h" +#include "ostream.h" +#include "ranges.h" + +#ifndef FMT_MODULE +# include +# if FMT_USE_LOCALE +# include +# endif +#endif + +FMT_BEGIN_NAMESPACE +namespace detail { + +template +using is_exotic_char = bool_constant::value>; + +template struct format_string_char {}; + +template +struct format_string_char< + S, void_t())))>> { + using type = char_t; +}; + +template +struct format_string_char< + S, enable_if_t::value>> { + using type = typename S::char_type; +}; + +template +using format_string_char_t = typename format_string_char::type; + +inline auto write_loc(basic_appender out, loc_value value, + const format_specs& specs, locale_ref loc) -> bool { +#if FMT_USE_LOCALE + auto& numpunct = + std::use_facet>(loc.get()); + auto separator = std::wstring(); + auto grouping = numpunct.grouping(); + if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep()); + return value.visit(loc_writer{out, specs, separator, grouping, {}}); +#endif + return false; +} + +template +void vformat_to(buffer& buf, basic_string_view fmt, + basic_format_args> args, + locale_ref loc = {}) { + static_assert(!std::is_same::value, ""); + auto out = basic_appender(buf); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} +} // namespace detail + +FMT_BEGIN_EXPORT + +using wstring_view = basic_string_view; +using wformat_parse_context = parse_context; +using wformat_context = buffered_context; +using wformat_args = basic_format_args; +using wmemory_buffer = basic_memory_buffer; + +template struct basic_fstring { + private: + basic_string_view str_; + + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + Char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + using t = basic_fstring; + + template >::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) { + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(s, checker(s, arg_pack())); + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) { + FMT_CONSTEXPR auto sv = basic_string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + basic_fstring(runtime_format_string fmt) : str_(fmt.str) {} + + operator basic_string_view() const { return str_; } + auto get() const -> basic_string_view { return str_; } +}; + +template +using basic_format_string = basic_fstring; + +template +using wformat_string = typename basic_format_string::t; +inline auto runtime(wstring_view s) -> runtime_format_string { + return {{s}}; +} + +template +constexpr auto make_wformat_args(T&... args) + -> decltype(fmt::make_format_args(args...)) { + return fmt::make_format_args(args...); +} + +#if !FMT_USE_NONTYPE_TEMPLATE_ARGS +inline namespace literals { +inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg { + return {s}; +} +} // namespace literals +#endif + +template +auto join(It begin, Sentinel end, wstring_view sep) + -> join_view { + return {begin, end, sep}; +} + +template ::value)> +auto join(Range&& range, wstring_view sep) + -> join_view { + return join(std::begin(range), std::end(range), sep); +} + +template +auto join(std::initializer_list list, wstring_view sep) + -> join_view { + return join(std::begin(list), std::end(list), sep); +} + +template ::value)> +auto join(const Tuple& tuple, basic_string_view sep) + -> tuple_join_view { + return {tuple, sep}; +} + +template ::value)> +auto vformat(basic_string_view fmt, + basic_format_args> args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, fmt, args); + return {buf.data(), buf.size()}; +} + +template +auto format(wformat_string fmt, T&&... args) -> std::wstring { + return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +auto format_to(OutputIt out, wformat_string fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt::wstring_view(fmt), + fmt::make_wformat_args(args...)); +} + +// Pass char_t as a default template parameter instead of using +// std::basic_string> to reduce the symbol size. +template , + FMT_ENABLE_IF(!std::is_same::value && + !std::is_same::value)> +auto format(const S& fmt, T&&... args) -> std::basic_string { + return vformat(detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto vformat(locale_ref loc, const S& fmt, + basic_format_args> args) + -> std::basic_string { + auto buf = basic_memory_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), args, loc); + return {buf.data(), buf.size()}; +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto format(locale_ref loc, const S& fmt, T&&... args) + -> std::basic_string { + return vformat(loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +auto vformat_to(OutputIt out, const S& fmt, + basic_format_args> args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, detail::to_string_view(fmt), args); + return detail::get_iterator(buf, out); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value && + !std::is_same::value && + !std::is_same::value)> +inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { + return vformat_to(out, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt, + basic_format_args> args) + -> OutputIt { + auto&& buf = detail::get_buffer(out); + vformat_to(buf, detail::to_string_view(fmt), args, loc); + return detail::get_iterator(buf, out); +} + +template , + bool enable = detail::is_output_iterator::value && + detail::is_exotic_char::value> +inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args) + -> typename std::enable_if::type { + return vformat_to(out, loc, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template ::value&& + detail::is_exotic_char::value)> +inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view fmt, + basic_format_args> args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args); + return {buf.out(), buf.count()}; +} + +template , + FMT_ENABLE_IF(detail::is_output_iterator::value&& + detail::is_exotic_char::value)> +inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args) + -> format_to_n_result { + return vformat_to_n(out, n, fmt::basic_string_view(fmt), + fmt::make_format_args>(args...)); +} + +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto formatted_size(const S& fmt, T&&... args) -> size_t { + auto buf = detail::counting_buffer(); + detail::vformat_to(buf, detail::to_string_view(fmt), + fmt::make_format_args>(args...)); + return buf.count(); +} + +inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, fmt, args); + buf.push_back(L'\0'); + if (std::fputws(buf.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 +void print(std::FILE* f, wformat_string fmt, T&&... args) { + return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template void print(wformat_string fmt, T&&... args) { + return vprint(wstring_view(fmt), fmt::make_wformat_args(args...)); +} + +template +void println(std::FILE* f, wformat_string fmt, T&&... args) { + return print(f, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +template void println(wformat_string fmt, T&&... args) { + return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +inline auto vformat(text_style ts, wstring_view fmt, wformat_args args) + -> std::wstring { + auto buf = wmemory_buffer(); + detail::vformat_to(buf, ts, fmt, args); + return {buf.data(), buf.size()}; +} + +template +inline auto format(text_style ts, wformat_string fmt, T&&... args) + -> std::wstring { + return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); +} + +inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { + auto buffer = basic_memory_buffer(); + detail::vformat_to(buffer, fmt, args); + detail::write_buffer(os, buffer); +} + +template +void print(std::wostream& os, wformat_string fmt, T&&... args) { + vprint(os, fmt, fmt::make_format_args>(args...)); +} + +template +void println(std::wostream& os, wformat_string fmt, T&&... args) { + print(os, L"{}\n", fmt::format(fmt, std::forward(args)...)); +} + +/// Converts `value` to `std::wstring` using the default format for type `T`. +template inline auto to_wstring(const T& value) -> std::wstring { + return format(FMT_STRING(L"{}"), value); +} +FMT_END_EXPORT +FMT_END_NAMESPACE + +#endif // FMT_XCHAR_H_ diff --git a/lib/spdlog/include/spdlog/fmt/chrono.h b/lib/spdlog/include/spdlog/fmt/chrono.h new file mode 100644 index 0000000..4da092b --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/chrono.h @@ -0,0 +1,23 @@ +// +// 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 +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/fmt/compile.h b/lib/spdlog/include/spdlog/fmt/compile.h new file mode 100644 index 0000000..795e6a3 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/compile.h @@ -0,0 +1,23 @@ +// +// 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 compile-time support +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/fmt/fmt.h b/lib/spdlog/include/spdlog/fmt/fmt.h new file mode 100644 index 0000000..6b1936d --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/fmt.h @@ -0,0 +1,26 @@ +// +// Copyright(c) 2016-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Include a bundled header-only copy of fmtlib or an external one. +// By default spdlog include its own copy. +// +#include + +#if defined(SPDLOG_USE_STD_FORMAT) // SPDLOG_USE_STD_FORMAT is defined - use std::format +#include +#elif !defined(SPDLOG_FMT_EXTERNAL) +#if !defined(SPDLOG_COMPILED_LIB) && !defined(FMT_HEADER_ONLY) +#define FMT_HEADER_ONLY +#endif +#ifndef FMT_USE_WINDOWS_H +#define FMT_USE_WINDOWS_H 0 +#endif +#include +#else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib +#include +#endif diff --git a/lib/spdlog/include/spdlog/fmt/ostr.h b/lib/spdlog/include/spdlog/fmt/ostr.h new file mode 100644 index 0000000..a9e50e1 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/ostr.h @@ -0,0 +1,23 @@ +// +// 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 +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/fmt/ranges.h b/lib/spdlog/include/spdlog/fmt/ranges.h new file mode 100644 index 0000000..117a9e4 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/ranges.h @@ -0,0 +1,23 @@ +// +// 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 ranges support +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/fmt/std.h b/lib/spdlog/include/spdlog/fmt/std.h new file mode 100644 index 0000000..4a3b828 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/std.h @@ -0,0 +1,24 @@ +// +// 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 std support (for formatting e.g. +// std::filesystem::path, std::thread::id, std::monostate, std::variant, ...) +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/fmt/xchar.h b/lib/spdlog/include/spdlog/fmt/xchar.h new file mode 100644 index 0000000..8ce1798 --- /dev/null +++ b/lib/spdlog/include/spdlog/fmt/xchar.h @@ -0,0 +1,23 @@ +// +// 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 xchar support +// +#include + +#if !defined(SPDLOG_USE_STD_FORMAT) +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifdef SPDLOG_HEADER_ONLY +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#endif +#include +#else +#include +#endif +#endif diff --git a/lib/spdlog/include/spdlog/formatter.h b/lib/spdlog/include/spdlog/formatter.h new file mode 100644 index 0000000..4d482f8 --- /dev/null +++ b/lib/spdlog/include/spdlog/formatter.h @@ -0,0 +1,17 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +namespace spdlog { + +class formatter { +public: + virtual ~formatter() = default; + virtual void format(const details::log_msg &msg, memory_buf_t &dest) = 0; + virtual std::unique_ptr clone() const = 0; +}; +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/fwd.h b/lib/spdlog/include/spdlog/fwd.h new file mode 100644 index 0000000..647b16b --- /dev/null +++ b/lib/spdlog/include/spdlog/fwd.h @@ -0,0 +1,18 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +namespace spdlog { +class logger; +class formatter; + +namespace sinks { +class sink; +} + +namespace level { +enum level_enum : int; +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/logger-inl.h b/lib/spdlog/include/spdlog/logger-inl.h new file mode 100644 index 0000000..381f11c --- /dev/null +++ b/lib/spdlog/include/spdlog/logger-inl.h @@ -0,0 +1,197 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include +#include + +#include + +namespace spdlog { + +// public methods +SPDLOG_INLINE logger::logger(const logger &other) + : name_(other.name_), + sinks_(other.sinks_), + level_(other.level_.load(std::memory_order_relaxed)), + flush_level_(other.flush_level_.load(std::memory_order_relaxed)), + custom_err_handler_(other.custom_err_handler_), + tracer_(other.tracer_) {} + +SPDLOG_INLINE logger::logger(logger &&other) SPDLOG_NOEXCEPT + : name_(std::move(other.name_)), + sinks_(std::move(other.sinks_)), + level_(other.level_.load(std::memory_order_relaxed)), + flush_level_(other.flush_level_.load(std::memory_order_relaxed)), + custom_err_handler_(std::move(other.custom_err_handler_)), + tracer_(std::move(other.tracer_)) + +{} + +SPDLOG_INLINE logger &logger::operator=(logger other) SPDLOG_NOEXCEPT { + this->swap(other); + return *this; +} + +SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT { + name_.swap(other.name_); + sinks_.swap(other.sinks_); + + // swap level_ + auto other_level = other.level_.load(); + auto my_level = level_.exchange(other_level); + other.level_.store(my_level); + + // swap flush level_ + other_level = other.flush_level_.load(); + my_level = flush_level_.exchange(other_level); + other.flush_level_.store(my_level); + + custom_err_handler_.swap(other.custom_err_handler_); + std::swap(tracer_, other.tracer_); +} + +SPDLOG_INLINE void swap(logger &a, logger &b) noexcept { a.swap(b); } + +SPDLOG_INLINE void logger::set_level(level::level_enum log_level) { level_.store(log_level); } + +SPDLOG_INLINE level::level_enum logger::level() const { + return static_cast(level_.load(std::memory_order_relaxed)); +} + +SPDLOG_INLINE const std::string &logger::name() const { return name_; } + +// set formatting for the sinks in this logger. +// each sink will get a separate instance of the formatter object. +SPDLOG_INLINE void logger::set_formatter(std::unique_ptr f) { + for (auto it = sinks_.begin(); it != sinks_.end(); ++it) { + if (std::next(it) == sinks_.end()) { + // last element - we can be move it. + (*it)->set_formatter(std::move(f)); + break; // to prevent clang-tidy warning + } else { + (*it)->set_formatter(f->clone()); + } + } +} + +SPDLOG_INLINE void logger::set_pattern(std::string pattern, pattern_time_type time_type) { + auto new_formatter = details::make_unique(std::move(pattern), time_type); + set_formatter(std::move(new_formatter)); +} + +// create new backtrace sink and move to it all our child sinks +SPDLOG_INLINE void logger::enable_backtrace(size_t n_messages) { tracer_.enable(n_messages); } + +// restore orig sinks and level and delete the backtrace sink +SPDLOG_INLINE void logger::disable_backtrace() { tracer_.disable(); } + +SPDLOG_INLINE void logger::dump_backtrace() { dump_backtrace_(); } + +// flush functions +SPDLOG_INLINE void logger::flush() { flush_(); } + +SPDLOG_INLINE void logger::flush_on(level::level_enum log_level) { flush_level_.store(log_level); } + +SPDLOG_INLINE level::level_enum logger::flush_level() const { + return static_cast(flush_level_.load(std::memory_order_relaxed)); +} + +// sinks +SPDLOG_INLINE const std::vector &logger::sinks() const { return sinks_; } + +SPDLOG_INLINE std::vector &logger::sinks() { return sinks_; } + +// error handler +SPDLOG_INLINE void logger::set_error_handler(err_handler handler) { + custom_err_handler_ = std::move(handler); +} + +// create new logger with same sinks and configuration. +SPDLOG_INLINE std::shared_ptr logger::clone(std::string logger_name) { + auto cloned = std::make_shared(*this); + cloned->name_ = std::move(logger_name); + return cloned; +} + +// protected methods +SPDLOG_INLINE void logger::log_it_(const spdlog::details::log_msg &log_msg, + bool log_enabled, + bool traceback_enabled) { + if (log_enabled) { + sink_it_(log_msg); + } + if (traceback_enabled) { + tracer_.push_back(log_msg); + } +} + +SPDLOG_INLINE void logger::sink_it_(const details::log_msg &msg) { + for (auto &sink : sinks_) { + if (sink->should_log(msg.level)) { + SPDLOG_TRY { sink->log(msg); } + SPDLOG_LOGGER_CATCH(msg.source) + } + } + + if (should_flush_(msg)) { + flush_(); + } +} + +SPDLOG_INLINE void logger::flush_() { + for (auto &sink : sinks_) { + SPDLOG_TRY { sink->flush(); } + SPDLOG_LOGGER_CATCH(source_loc()) + } +} + +SPDLOG_INLINE void logger::dump_backtrace_() { + using details::log_msg; + if (tracer_.enabled() && !tracer_.empty()) { + sink_it_( + log_msg{name(), level::info, "****************** Backtrace Start ******************"}); + tracer_.foreach_pop([this](const log_msg &msg) { this->sink_it_(msg); }); + sink_it_( + log_msg{name(), level::info, "****************** Backtrace End ********************"}); + } +} + +SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) const { + return (msg.level >= flush_level()) && (msg.level != level::off); +} + +SPDLOG_INLINE void logger::err_handler_(const std::string &msg) const { + if (custom_err_handler_) { + custom_err_handler_(msg); + } else { + using std::chrono::system_clock; + static std::mutex mutex; + static std::chrono::system_clock::time_point last_report_time; + static size_t err_counter = 0; + std::lock_guard lk{mutex}; + auto now = system_clock::now(); + err_counter++; + if (now - last_report_time < std::chrono::seconds(1)) { + return; + } + last_report_time = now; + auto tm_time = details::os::localtime(system_clock::to_time_t(now)); + char date_buf[64]; + std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); +#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 diff --git a/lib/spdlog/include/spdlog/logger.h b/lib/spdlog/include/spdlog/logger.h new file mode 100644 index 0000000..9db3c6b --- /dev/null +++ b/lib/spdlog/include/spdlog/logger.h @@ -0,0 +1,379 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// Thread safe logger (except for set_error_handler()) +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message and if yes: +// 2. Call the underlying sinks to do the job. +// 3. Each sink use its own private copy of a formatter to format the message +// and send to its destination. +// +// The use of private formatter per sink provides the opportunity to cache some +// formatted data, and support for different format per sink. + +#include +#include +#include + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +#ifndef _WIN32 +#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows +#endif +#include +#endif + +#include + +#ifndef SPDLOG_NO_EXCEPTIONS +#define SPDLOG_LOGGER_CATCH(location) \ + catch (const std::exception &ex) { \ + if (location.filename) { \ + err_handler_(fmt_lib::format(SPDLOG_FMT_STRING("{} [{}({})]"), ex.what(), \ + location.filename, location.line)); \ + } else { \ + err_handler_(ex.what()); \ + } \ + } \ + catch (...) { \ + err_handler_("Rethrowing unknown exception in logger"); \ + throw; \ + } +#else +#define SPDLOG_LOGGER_CATCH(location) +#endif + +namespace spdlog { + +class SPDLOG_API logger { +public: + // Empty logger + explicit logger(std::string name) + : name_(std::move(name)), + sinks_() {} + + // Logger with range on sinks + template + logger(std::string name, It begin, It end) + : name_(std::move(name)), + sinks_(begin, end) {} + + // Logger with single sink + logger(std::string name, sink_ptr single_sink) + : logger(std::move(name), {std::move(single_sink)}) {} + + // Logger with sinks init list + logger(std::string name, sinks_init_list sinks) + : logger(std::move(name), sinks.begin(), sinks.end()) {} + + virtual ~logger() = default; + + logger(const logger &other); + logger(logger &&other) SPDLOG_NOEXCEPT; + logger &operator=(logger other) SPDLOG_NOEXCEPT; + void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; + + template + void log(source_loc loc, level::level_enum lvl, format_string_t fmt, Args &&...args) { + log_(loc, lvl, details::to_string_view(fmt), std::forward(args)...); + } + + template + void log(level::level_enum lvl, format_string_t fmt, Args &&...args) { + log(source_loc{}, lvl, fmt, std::forward(args)...); + } + + template + void log(level::level_enum lvl, const T &msg) { + log(source_loc{}, lvl, msg); + } + + // T cannot be statically converted to format string (including string_view/wstring_view) + template ::value, + int>::type = 0> + void log(source_loc loc, level::level_enum lvl, const T &msg) { + log(loc, lvl, "{}", msg); + } + + void log(log_clock::time_point log_time, + source_loc loc, + level::level_enum lvl, + string_view_t msg) { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) { + return; + } + + details::log_msg log_msg(log_time, loc, name_, lvl, msg); + log_it_(log_msg, log_enabled, traceback_enabled); + } + + void log(source_loc loc, level::level_enum lvl, string_view_t msg) { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) { + return; + } + + details::log_msg log_msg(loc, name_, lvl, msg); + log_it_(log_msg, log_enabled, traceback_enabled); + } + + void log(level::level_enum lvl, string_view_t msg) { log(source_loc{}, lvl, msg); } + + template + void trace(format_string_t fmt, Args &&...args) { + log(level::trace, fmt, std::forward(args)...); + } + + template + void debug(format_string_t fmt, Args &&...args) { + log(level::debug, fmt, std::forward(args)...); + } + + template + void info(format_string_t fmt, Args &&...args) { + log(level::info, fmt, std::forward(args)...); + } + + template + void warn(format_string_t fmt, Args &&...args) { + log(level::warn, fmt, std::forward(args)...); + } + + template + void error(format_string_t fmt, Args &&...args) { + log(level::err, fmt, std::forward(args)...); + } + + template + void critical(format_string_t fmt, Args &&...args) { + log(level::critical, fmt, std::forward(args)...); + } + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template + void log(source_loc loc, level::level_enum lvl, wformat_string_t fmt, Args &&...args) { + log_(loc, lvl, details::to_string_view(fmt), std::forward(args)...); + } + + template + void log(level::level_enum lvl, wformat_string_t fmt, Args &&...args) { + log(source_loc{}, lvl, fmt, std::forward(args)...); + } + + void log(log_clock::time_point log_time, + source_loc loc, + level::level_enum lvl, + wstring_view_t msg) { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) { + return; + } + + memory_buf_t buf; + details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.size()), buf); + details::log_msg log_msg(log_time, loc, name_, lvl, string_view_t(buf.data(), buf.size())); + log_it_(log_msg, log_enabled, traceback_enabled); + } + + void log(source_loc loc, level::level_enum lvl, wstring_view_t msg) { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) { + return; + } + + memory_buf_t buf; + details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.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); + } + + void log(level::level_enum lvl, wstring_view_t msg) { log(source_loc{}, lvl, msg); } + + template + void trace(wformat_string_t fmt, Args &&...args) { + log(level::trace, fmt, std::forward(args)...); + } + + template + void debug(wformat_string_t fmt, Args &&...args) { + log(level::debug, fmt, std::forward(args)...); + } + + template + void info(wformat_string_t fmt, Args &&...args) { + log(level::info, fmt, std::forward(args)...); + } + + template + void warn(wformat_string_t fmt, Args &&...args) { + log(level::warn, fmt, std::forward(args)...); + } + + template + void error(wformat_string_t fmt, Args &&...args) { + log(level::err, fmt, std::forward(args)...); + } + + template + void critical(wformat_string_t fmt, Args &&...args) { + log(level::critical, fmt, std::forward(args)...); + } +#endif + + template + void trace(const T &msg) { + log(level::trace, msg); + } + + template + void debug(const T &msg) { + log(level::debug, msg); + } + + template + void info(const T &msg) { + log(level::info, msg); + } + + template + void warn(const T &msg) { + log(level::warn, msg); + } + + template + void error(const T &msg) { + log(level::err, msg); + } + + template + void critical(const T &msg) { + log(level::critical, msg); + } + + // return true logging is enabled for the given level. + bool should_log(level::level_enum msg_level) const { + return msg_level >= level_.load(std::memory_order_relaxed); + } + + // return true if backtrace logging is enabled. + bool should_backtrace() const { return tracer_.enabled(); } + + void set_level(level::level_enum log_level); + + level::level_enum level() const; + + const std::string &name() const; + + // set formatting for the sinks in this logger. + // each sink will get a separate instance of the formatter object. + void set_formatter(std::unique_ptr f); + + // set formatting for the sinks in this logger. + // equivalent to + // set_formatter(make_unique(pattern, time_type)) + // Note: each sink will get a new instance of a formatter object, replacing the old one. + void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); + + // backtrace support. + // efficiently store all debug/trace messages in a circular buffer until needed for debugging. + void enable_backtrace(size_t n_messages); + void disable_backtrace(); + void dump_backtrace(); + + // flush functions + void flush(); + void flush_on(level::level_enum log_level); + level::level_enum flush_level() const; + + // sinks + const std::vector &sinks() const; + + std::vector &sinks(); + + // error handler + void set_error_handler(err_handler); + + // create new logger with same sinks and configuration. + virtual std::shared_ptr clone(std::string logger_name); + +protected: + std::string name_; + std::vector sinks_; + spdlog::level_t level_{level::info}; + spdlog::level_t flush_level_{level::off}; + err_handler custom_err_handler_{nullptr}; + details::backtracer tracer_; + + // common implementation for after templated public api has been resolved + template + void log_(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&...args) { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) { + return; + } + SPDLOG_TRY { + memory_buf_t buf; +#ifdef SPDLOG_USE_STD_FORMAT + fmt_lib::vformat_to(std::back_inserter(buf), fmt, fmt_lib::make_format_args(args...)); +#else + fmt::vformat_to(fmt::appender(buf), fmt, fmt::make_format_args(args...)); +#endif + + 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(loc) + } + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + template + void log_(source_loc loc, level::level_enum lvl, 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 + wmemory_buf_t wbuf; + fmt_lib::vformat_to(std::back_inserter(wbuf), fmt, + fmt_lib::make_format_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(loc) + } +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + // log the given message (if the given log level is high enough), + // and save backtrace (if backtrace is enabled). + void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled); + virtual void sink_it_(const details::log_msg &msg); + virtual void flush_(); + void dump_backtrace_(); + bool should_flush_(const details::log_msg &msg) const; + + // handle errors during logging. + // default handler prints the error to stderr at max rate of 1 message/sec. + void err_handler_(const std::string &msg) const; +}; + +void swap(logger &a, logger &b) noexcept; + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "logger-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/mdc.h b/lib/spdlog/include/spdlog/mdc.h new file mode 100644 index 0000000..2f2665a --- /dev/null +++ b/lib/spdlog/include/spdlog/mdc.h @@ -0,0 +1,52 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#if defined(SPDLOG_NO_TLS) +#error "This header requires thread local storage support, but SPDLOG_NO_TLS is defined." +#endif + +#include +#include + +#include + +// MDC is a simple map of key->string values stored in thread local storage whose content will be +// printed by the loggers. Note: Not supported in async mode (thread local storage - so the async +// thread pool have different copy). +// +// Usage example: +// spdlog::mdc::put("mdc_key_1", "mdc_value_1"); +// spdlog::info("Hello, {}", "World!"); // => [2024-04-26 02:08:05.040] [info] +// [mdc_key_1:mdc_value_1] Hello, World! + +namespace spdlog { +class SPDLOG_API mdc { +public: + using mdc_map_t = std::map; + + static void put(const std::string &key, const std::string &value) { + get_context()[key] = value; + } + + static std::string get(const std::string &key) { + auto &context = get_context(); + auto it = context.find(key); + if (it != context.end()) { + return it->second; + } + return ""; + } + + static void remove(const std::string &key) { get_context().erase(key); } + + static void clear() { get_context().clear(); } + + static mdc_map_t &get_context() { + static thread_local mdc_map_t context; + return context; + } +}; + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/pattern_formatter-inl.h b/lib/spdlog/include/spdlog/pattern_formatter-inl.h new file mode 100644 index 0000000..f650627 --- /dev/null +++ b/lib/spdlog/include/spdlog/pattern_formatter-inl.h @@ -0,0 +1,1350 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include +#include + +#ifndef SPDLOG_NO_TLS +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace details { + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appender +/////////////////////////////////////////////////////////////////////// + +class scoped_padder { +public: + scoped_padder(size_t wrapped_size, const padding_info &padinfo, memory_buf_t &dest) + : padinfo_(padinfo), + dest_(dest) { + remaining_pad_ = static_cast(padinfo.width_) - static_cast(wrapped_size); + if (remaining_pad_ <= 0) { + return; + } + + if (padinfo_.side_ == padding_info::pad_side::left) { + pad_it(remaining_pad_); + remaining_pad_ = 0; + } else if (padinfo_.side_ == padding_info::pad_side::center) { + auto half_pad = remaining_pad_ / 2; + auto reminder = remaining_pad_ & 1; + pad_it(half_pad); + remaining_pad_ = half_pad + reminder; // for the right side + } + } + + template + static unsigned int count_digits(T n) { + return fmt_helper::count_digits(n); + } + + ~scoped_padder() { + if (remaining_pad_ >= 0) { + pad_it(remaining_pad_); + } else if (padinfo_.truncate_) { + long new_size = static_cast(dest_.size()) + remaining_pad_; + if (new_size < 0) { + new_size = 0; + } + dest_.resize(static_cast(new_size)); + } + } + +private: + void pad_it(long count) { + fmt_helper::append_string_view(string_view_t(spaces_.data(), static_cast(count)), + dest_); + } + + const padding_info &padinfo_; + memory_buf_t &dest_; + long remaining_pad_; + string_view_t spaces_{" ", 64}; +}; + +struct null_scoped_padder { + null_scoped_padder(size_t /*wrapped_size*/, + const padding_info & /*padinfo*/, + memory_buf_t & /*dest*/) {} + + template + static unsigned int count_digits(T /* number */) { + return 0; + } +}; + +template +class name_formatter final : public flag_formatter { +public: + explicit name_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + ScopedPadder p(msg.logger_name.size(), padinfo_, dest); + fmt_helper::append_string_view(msg.logger_name, dest); + } +}; + +// log level appender +template +class level_formatter final : public flag_formatter { +public: + explicit level_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + const string_view_t &level_name = level::to_string_view(msg.level); + ScopedPadder p(level_name.size(), padinfo_, dest); + fmt_helper::append_string_view(level_name, dest); + } +}; + +// short log level appender +template +class short_level_formatter final : public flag_formatter { +public: + explicit short_level_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + string_view_t level_name{level::to_short_c_str(msg.level)}; + ScopedPadder p(level_name.size(), padinfo_, dest); + fmt_helper::append_string_view(level_name, dest); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char *ampm(const tm &t) { return t.tm_hour >= 12 ? "PM" : "AM"; } + +static int to12h(const tm &t) { return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; } + +// Abbreviated weekday name +static std::array days{{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}}; + +template +class a_formatter final : public flag_formatter { +public: + explicit a_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + string_view_t field_value{days[static_cast(tm_time.tm_wday)]}; + ScopedPadder p(field_value.size(), padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); + } +}; + +// Full weekday name +static std::array full_days{ + {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}}; + +template +class A_formatter : public flag_formatter { +public: + explicit A_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + string_view_t field_value{full_days[static_cast(tm_time.tm_wday)]}; + ScopedPadder p(field_value.size(), padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); + } +}; + +// Abbreviated month +static const std::array months{ + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}}; + +template +class b_formatter final : public flag_formatter { +public: + explicit b_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + string_view_t field_value{months[static_cast(tm_time.tm_mon)]}; + ScopedPadder p(field_value.size(), padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); + } +}; + +// Full month name +static const std::array full_months{{"January", "February", "March", "April", + "May", "June", "July", "August", "September", + "October", "November", "December"}}; + +template +class B_formatter final : public flag_formatter { +public: + explicit B_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + string_view_t field_value{full_months[static_cast(tm_time.tm_mon)]}; + ScopedPadder p(field_value.size(), padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); + } +}; + +// Date and time representation (Thu Aug 23 15:35:46 2014) +template +class c_formatter final : public flag_formatter { +public: + explicit c_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 24; + ScopedPadder p(field_size, padinfo_, dest); + + fmt_helper::append_string_view(days[static_cast(tm_time.tm_wday)], dest); + dest.push_back(' '); + fmt_helper::append_string_view(months[static_cast(tm_time.tm_mon)], dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_mday, dest); + dest.push_back(' '); + // time + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// year - 2 digit +template +class C_formatter final : public flag_formatter { +public: + explicit C_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +template +class D_formatter final : public flag_formatter { +public: + explicit D_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 8; + ScopedPadder p(field_size, padinfo_, dest); + + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_mday, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// year - 4 digit +template +class Y_formatter final : public flag_formatter { +public: + explicit Y_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 4; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// month 1-12 +template +class m_formatter final : public flag_formatter { +public: + explicit m_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + } +}; + +// day of month 1-31 +template +class d_formatter final : public flag_formatter { +public: + explicit d_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_mday, dest); + } +}; + +// hours in 24 format 0-23 +template +class H_formatter final : public flag_formatter { +public: + explicit H_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_hour, dest); + } +}; + +// hours in 12 format 1-12 +template +class I_formatter final : public flag_formatter { +public: + explicit I_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(to12h(tm_time), dest); + } +}; + +// minutes 0-59 +template +class M_formatter final : public flag_formatter { +public: + explicit M_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// seconds 0-59 +template +class S_formatter final : public flag_formatter { +public: + explicit S_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// milliseconds +template +class e_formatter final : public flag_formatter { +public: + explicit e_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + auto millis = fmt_helper::time_fraction(msg.time); + const size_t field_size = 3; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad3(static_cast(millis.count()), dest); + } +}; + +// microseconds +template +class f_formatter final : public flag_formatter { +public: + explicit f_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + auto micros = fmt_helper::time_fraction(msg.time); + + const size_t field_size = 6; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad6(static_cast(micros.count()), dest); + } +}; + +// nanoseconds +template +class F_formatter final : public flag_formatter { +public: + explicit F_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + auto ns = fmt_helper::time_fraction(msg.time); + const size_t field_size = 9; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::pad9(static_cast(ns.count()), dest); + } +}; + +// seconds since epoch +template +class E_formatter final : public flag_formatter { +public: + explicit E_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + const size_t field_size = 10; + ScopedPadder p(field_size, padinfo_, dest); + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration).count(); + fmt_helper::append_int(seconds, dest); + } +}; + +// AM/PM +template +class p_formatter final : public flag_formatter { +public: + explicit p_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 2; + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::append_string_view(ampm(tm_time), dest); + } +}; + +// 12 hour clock 02:55:02 pm +template +class r_formatter final : public flag_formatter { +public: + explicit r_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 11; + ScopedPadder p(field_size, padinfo_, dest); + + fmt_helper::pad2(to12h(tm_time), dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_string_view(ampm(tm_time), dest); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +template +class R_formatter final : public flag_formatter { +public: + explicit R_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 5; + ScopedPadder p(field_size, padinfo_, dest); + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +template +class T_formatter final : public flag_formatter { +public: + explicit T_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 8; + ScopedPadder p(field_size, padinfo_, dest); + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +// If SPDLOG_NO_TZ_OFFSET is defined, print "+??.??" instead. +template +class z_formatter final : public flag_formatter { +public: + explicit z_formatter(padding_info padinfo, pattern_time_type time_type) + : flag_formatter(padinfo), + time_type_(time_type) {} + + z_formatter() = default; + z_formatter(const z_formatter &) = delete; + z_formatter &operator=(const z_formatter &) = delete; + + void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override { + const size_t field_size = 6; + ScopedPadder p(field_size, padinfo_, dest); +#ifdef SPDLOG_NO_TZ_OFFSET + const char *str = "+??:??"; + dest.append(str, str + 6); +#else + if (time_type_ == pattern_time_type::utc) { + const char *zeroes = "+00:00"; + dest.append(zeroes, zeroes + 6); + return; + } + auto total_minutes = get_cached_offset(msg, tm_time); + bool is_negative = total_minutes < 0; + if (is_negative) { + total_minutes = -total_minutes; + dest.push_back('-'); + } else { + dest.push_back('+'); + } + + fmt_helper::pad2(total_minutes / 60, dest); // hours + dest.push_back(':'); + fmt_helper::pad2(total_minutes % 60, dest); // minutes +#endif // SPDLOG_NO_TZ_OFFSET + } + +private: + pattern_time_type time_type_; + log_clock::time_point last_update_{std::chrono::seconds(0)}; + int offset_minutes_{0}; + + int get_cached_offset(const log_msg &msg, const std::tm &tm_time) { + // refresh every 10 seconds + if (msg.time - last_update_ >= std::chrono::seconds(10)) { + offset_minutes_ = os::utc_minutes_offset(tm_time); + last_update_ = msg.time; + } + return offset_minutes_; + } +}; + +// Thread id +template +class t_formatter final : public flag_formatter { +public: + explicit t_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + const auto field_size = ScopedPadder::count_digits(msg.thread_id); + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::append_int(msg.thread_id, dest); + } +}; + +// Current pid +template +class pid_formatter final : public flag_formatter { +public: + explicit pid_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override { + const auto pid = static_cast(details::os::pid()); + auto field_size = ScopedPadder::count_digits(pid); + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::append_int(pid, dest); + } +}; + +template +class v_formatter final : public flag_formatter { +public: + explicit v_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + ScopedPadder p(msg.payload.size(), padinfo_, dest); + fmt_helper::append_string_view(msg.payload, dest); + } +}; + +class ch_formatter final : public flag_formatter { +public: + explicit ch_formatter(char ch) + : ch_(ch) {} + + void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override { + dest.push_back(ch_); + } + +private: + char ch_; +}; + +// aggregate user chars to display as is +class aggregate_formatter final : public flag_formatter { +public: + aggregate_formatter() = default; + + void add_ch(char ch) { str_ += ch; } + void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override { + fmt_helper::append_string_view(str_, dest); + } + +private: + std::string str_; +}; + +// mark the color range. expect it to be in the form of "%^colored text%$" +class color_start_formatter final : public flag_formatter { +public: + explicit color_start_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + msg.color_range_start = dest.size(); + } +}; + +class color_stop_formatter final : public flag_formatter { +public: + explicit color_stop_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + msg.color_range_end = dest.size(); + } +}; + +// print source location +template +class source_location_formatter final : public flag_formatter { +public: + explicit source_location_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + if (msg.source.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } + + size_t text_size; + if (padinfo_.enabled()) { + // calc text size for padding based on "filename:line" + text_size = std::char_traits::length(msg.source.filename) + + ScopedPadder::count_digits(msg.source.line) + 1; + } else { + text_size = 0; + } + + ScopedPadder p(text_size, padinfo_, dest); + fmt_helper::append_string_view(msg.source.filename, dest); + dest.push_back(':'); + fmt_helper::append_int(msg.source.line, dest); + } +}; + +// print source filename +template +class source_filename_formatter final : public flag_formatter { +public: + explicit source_filename_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + if (msg.source.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } + size_t text_size = + padinfo_.enabled() ? std::char_traits::length(msg.source.filename) : 0; + ScopedPadder p(text_size, padinfo_, dest); + fmt_helper::append_string_view(msg.source.filename, dest); + } +}; + +template +class short_filename_formatter final : public flag_formatter { +public: + explicit short_filename_formatter(padding_info 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) { + // 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; + } else { + const std::reverse_iterator begin(filename + std::strlen(filename)); + const std::reverse_iterator 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 { + if (msg.source.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } + auto filename = basename(msg.source.filename); + size_t text_size = padinfo_.enabled() ? std::char_traits::length(filename) : 0; + ScopedPadder p(text_size, padinfo_, dest); + fmt_helper::append_string_view(filename, dest); + } +}; + +template +class source_linenum_formatter final : public flag_formatter { +public: + explicit source_linenum_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + if (msg.source.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } + + auto field_size = ScopedPadder::count_digits(msg.source.line); + ScopedPadder p(field_size, padinfo_, dest); + fmt_helper::append_int(msg.source.line, dest); + } +}; + +// print source funcname +template +class source_funcname_formatter final : public flag_formatter { +public: + explicit source_funcname_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + if (msg.source.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } + size_t text_size = + padinfo_.enabled() ? std::char_traits::length(msg.source.funcname) : 0; + ScopedPadder p(text_size, padinfo_, dest); + fmt_helper::append_string_view(msg.source.funcname, dest); + } +}; + +// print elapsed time since last message +template +class elapsed_formatter final : public flag_formatter { +public: + using DurationUnits = Units; + + explicit elapsed_formatter(padding_info padinfo) + : flag_formatter(padinfo), + last_message_time_(log_clock::now()) {} + + void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override { + auto delta = (std::max)(msg.time - last_message_time_, log_clock::duration::zero()); + auto delta_units = std::chrono::duration_cast(delta); + last_message_time_ = msg.time; + auto delta_count = static_cast(delta_units.count()); + auto n_digits = static_cast(ScopedPadder::count_digits(delta_count)); + ScopedPadder p(n_digits, padinfo_, dest); + fmt_helper::append_int(delta_count, dest); + } + +private: + log_clock::time_point last_message_time_; +}; + +// Class for formatting Mapped Diagnostic Context (MDC) in log messages. +// Example: [logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message +#ifndef SPDLOG_NO_TLS +template +class mdc_formatter : public flag_formatter { +public: + explicit mdc_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override { + const auto &mdc_map = mdc::get_context(); + if (mdc_map.empty()) { + ScopedPadder p(0, padinfo_, dest); + return; + } else { + format_mdc(mdc_map, dest); + } + } + + void format_mdc(const mdc::mdc_map_t &mdc_map, memory_buf_t &dest) { + const auto last_element = std::prev(mdc_map.end()); + for (auto it = mdc_map.begin(); it != mdc_map.end(); ++it) { + const auto &key = it->first; + const auto &value = it->second; + size_t content_size = key.size() + value.size() + 1; // 1 for ':' + + if (it != last_element) { + content_size++; // 1 for ' ' + } + + ScopedPadder p(content_size, padinfo_, dest); + fmt_helper::append_string_view(key, dest); + fmt_helper::append_string_view(":", dest); + fmt_helper::append_string_view(value, dest); + if (it != last_element) { + fmt_helper::append_string_view(" ", dest); + } + } + } +}; +#endif + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v +class full_formatter final : public flag_formatter { +public: + explicit full_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::seconds; + + // cache the date/time part for the next second. + auto duration = msg.time.time_since_epoch(); + auto secs = duration_cast(duration); + + if (cache_timestamp_ != secs || cached_datetime_.size() == 0) { + cached_datetime_.clear(); + cached_datetime_.push_back('['); + fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mday, cached_datetime_); + cached_datetime_.push_back(' '); + + fmt_helper::pad2(tm_time.tm_hour, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_min, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_sec, cached_datetime_); + cached_datetime_.push_back('.'); + + cache_timestamp_ = secs; + } + dest.append(cached_datetime_.begin(), cached_datetime_.end()); + + auto millis = fmt_helper::time_fraction(msg.time); + fmt_helper::pad3(static_cast(millis.count()), dest); + dest.push_back(']'); + dest.push_back(' '); + + // append logger name if exists + if (msg.logger_name.size() > 0) { + dest.push_back('['); + fmt_helper::append_string_view(msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); + } + + dest.push_back('['); + // wrap the level name with color + msg.color_range_start = dest.size(); + // fmt_helper::append_string_view(level::to_c_str(msg.level), dest); + fmt_helper::append_string_view(level::to_string_view(msg.level), dest); + msg.color_range_end = dest.size(); + dest.push_back(']'); + dest.push_back(' '); + + // add source location if present + if (!msg.source.empty()) { + dest.push_back('['); + const char *filename = + details::short_filename_formatter::basename( + msg.source.filename); + fmt_helper::append_string_view(filename, dest); + dest.push_back(':'); + fmt_helper::append_int(msg.source.line, dest); + dest.push_back(']'); + dest.push_back(' '); + } + +#ifndef SPDLOG_NO_TLS + // add mdc if present + auto &mdc_map = mdc::get_context(); + if (!mdc_map.empty()) { + dest.push_back('['); + mdc_formatter_.format_mdc(mdc_map, dest); + dest.push_back(']'); + dest.push_back(' '); + } +#endif + // fmt_helper::append_string_view(msg.msg(), dest); + fmt_helper::append_string_view(msg.payload, dest); + } + +private: + std::chrono::seconds cache_timestamp_{0}; + memory_buf_t cached_datetime_; + +#ifndef SPDLOG_NO_TLS + mdc_formatter mdc_formatter_{padding_info {}}; +#endif +}; + +} // namespace details + +SPDLOG_INLINE pattern_formatter::pattern_formatter(std::string pattern, + pattern_time_type time_type, + std::string eol, + custom_flags custom_user_flags) + : pattern_(std::move(pattern)), + eol_(std::move(eol)), + pattern_time_type_(time_type), + need_localtime_(false), + last_log_secs_(0), + custom_handlers_(std::move(custom_user_flags)) { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + compile_pattern_(pattern_); +} + +// use by default full formatter for if pattern is not given +SPDLOG_INLINE pattern_formatter::pattern_formatter(pattern_time_type time_type, std::string eol) + : pattern_("%+"), + eol_(std::move(eol)), + pattern_time_type_(time_type), + need_localtime_(true), + last_log_secs_(0) { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + formatters_.push_back(details::make_unique(details::padding_info{})); +} + +SPDLOG_INLINE std::unique_ptr pattern_formatter::clone() const { + custom_flags cloned_custom_formatters; + for (auto &it : custom_handlers_) { + cloned_custom_formatters[it.first] = it.second->clone(); + } + auto cloned = details::make_unique(pattern_, pattern_time_type_, eol_, + std::move(cloned_custom_formatters)); + cloned->need_localtime(need_localtime_); +#if defined(__GNUC__) && __GNUC__ < 5 + return std::move(cloned); +#else + return cloned; +#endif +} + +SPDLOG_INLINE void pattern_formatter::format(const details::log_msg &msg, memory_buf_t &dest) { + if (need_localtime_) { + const auto secs = + std::chrono::duration_cast(msg.time.time_since_epoch()); + if (secs != last_log_secs_) { + cached_tm_ = get_time_(msg); + last_log_secs_ = secs; + } + } + + for (auto &f : formatters_) { + f->format(msg, cached_tm_, dest); + } + // write eol + details::fmt_helper::append_string_view(eol_, dest); +} + +SPDLOG_INLINE void pattern_formatter::set_pattern(std::string pattern) { + pattern_ = std::move(pattern); + need_localtime_ = false; + compile_pattern_(pattern_); +} + +SPDLOG_INLINE void pattern_formatter::need_localtime(bool need) { need_localtime_ = need; } + +SPDLOG_INLINE std::tm pattern_formatter::get_time_(const details::log_msg &msg) const { + if (pattern_time_type_ == pattern_time_type::local) { + return details::os::localtime(log_clock::to_time_t(msg.time)); + } + return details::os::gmtime(log_clock::to_time_t(msg.time)); +} + +template +SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_info padding) { + // process custom flags + auto it = custom_handlers_.find(flag); + if (it != custom_handlers_.end()) { + auto custom_handler = it->second->clone(); + custom_handler->set_padding_info(padding); + formatters_.push_back(std::move(custom_handler)); + return; + } + + // process built-in flags + switch (flag) { + case ('+'): // default formatter + formatters_.push_back(details::make_unique(padding)); + need_localtime_ = true; + break; + + case 'n': // logger name + formatters_.push_back(details::make_unique>(padding)); + break; + + case 'l': // level + formatters_.push_back(details::make_unique>(padding)); + break; + + case 'L': // short level + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('t'): // thread id + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('v'): // the message text + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('a'): // weekday + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('A'): // short weekday + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('b'): + case ('h'): // month + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('B'): // short month + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('c'): // datetime + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('C'): // year 2 digits + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('Y'): // year 4 digits + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('D'): + case ('x'): // datetime MM/DD/YY + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('m'): // month 1-12 + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('d'): // day of month 1-31 + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('H'): // hours 24 + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('I'): // hours 12 + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('M'): // minutes + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('S'): // seconds + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('e'): // milliseconds + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('f'): // microseconds + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('F'): // nanoseconds + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('E'): // seconds since epoch + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('p'): // am/pm + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('r'): // 12 hour clock 02:55:02 pm + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('R'): // 24-hour HH:MM time + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + + case ('T'): + case ('X'): // ISO 8601 time format (HH:MM:SS) + formatters_.push_back(details::make_unique>(padding)); + need_localtime_ = true; + break; + case ('z'): // timezone + formatters_.push_back( + details::make_unique>(padding, pattern_time_type_)); + need_localtime_ = true; + break; + case ('P'): // pid + formatters_.push_back(details::make_unique>(padding)); + break; + + case ('^'): // color range start + formatters_.push_back(details::make_unique(padding)); + break; + + case ('$'): // color range end + formatters_.push_back(details::make_unique(padding)); + break; + + case ('@'): // source location (filename:filenumber) + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('s'): // short source filename - without directory name + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('g'): // full source filename + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('#'): // source line number + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('!'): // source funcname + formatters_.push_back( + details::make_unique>(padding)); + break; + + case ('%'): // % char + formatters_.push_back(details::make_unique('%')); + break; + + case ('u'): // elapsed time since last log message in nanos + formatters_.push_back( + details::make_unique>( + padding)); + break; + + case ('i'): // elapsed time since last log message in micros + formatters_.push_back( + details::make_unique>( + padding)); + break; + + case ('o'): // elapsed time since last log message in millis + formatters_.push_back( + details::make_unique>( + padding)); + break; + + case ('O'): // elapsed time since last log message in seconds + formatters_.push_back( + details::make_unique>( + padding)); + break; + +#ifndef SPDLOG_NO_TLS // mdc formatter requires TLS support + case ('&'): + formatters_.push_back(details::make_unique>(padding)); + break; +#endif + + default: // Unknown flag appears as is + auto unknown_flag = details::make_unique(); + + if (!padding.truncate_) { + unknown_flag->add_ch('%'); + unknown_flag->add_ch(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>(padding)); + unknown_flag->add_ch(flag); + formatters_.push_back((std::move(unknown_flag))); + } + + break; + } +} + +// Extract given pad spec (e.g. %8X, %=8X, %-8!X, %8!X, %=8!X, %-8!X, %+8!X) +// Advance the given it pass the end of the padding spec found (if any) +// Return padding. +SPDLOG_INLINE details::padding_info pattern_formatter::handle_padspec_( + std::string::const_iterator &it, std::string::const_iterator end) { + using details::padding_info; + using details::scoped_padder; + const size_t max_width = 64; + if (it == end) { + return padding_info{}; + } + + padding_info::pad_side side; + switch (*it) { + case '-': + side = padding_info::pad_side::right; + ++it; + break; + case '=': + side = padding_info::pad_side::center; + ++it; + break; + default: + side = details::padding_info::pad_side::left; + break; + } + + if (it == end || !std::isdigit(static_cast(*it))) { + return padding_info{}; // no padding if no digit found here + } + + auto width = static_cast(*it) - '0'; + for (++it; it != end && std::isdigit(static_cast(*it)); ++it) { + auto digit = static_cast(*it) - '0'; + width = width * 10 + digit; + } + + // search for the optional truncate marker '!' + bool truncate; + if (it != end && *it == '!') { + truncate = true; + ++it; + } else { + truncate = false; + } + return details::padding_info{std::min(width, max_width), side, truncate}; +} + +SPDLOG_INLINE void pattern_formatter::compile_pattern_(const std::string &pattern) { + auto end = pattern.end(); + std::unique_ptr user_chars; + formatters_.clear(); + for (auto it = pattern.begin(); it != end; ++it) { + if (*it == '%') { + if (user_chars) // append user chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + + auto padding = handle_padspec_(++it, end); + + if (it != end) { + if (padding.enabled()) { + handle_flag_(*it, padding); + } else { + handle_flag_(*it, padding); + } + } else { + break; + } + } else // chars not following the % sign should be displayed as is + { + if (!user_chars) { + user_chars = details::make_unique(); + } + user_chars->add_ch(*it); + } + } + if (user_chars) // append raw chars found so far + { + formatters_.push_back(std::move(user_chars)); + } +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/pattern_formatter.h b/lib/spdlog/include/spdlog/pattern_formatter.h new file mode 100644 index 0000000..42a7509 --- /dev/null +++ b/lib/spdlog/include/spdlog/pattern_formatter.h @@ -0,0 +1,118 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace spdlog { +namespace details { + +// padding information. +struct padding_info { + enum class pad_side { left, right, center }; + + padding_info() = default; + padding_info(size_t width, padding_info::pad_side side, bool truncate) + : width_(width), + side_(side), + truncate_(truncate), + enabled_(true) {} + + bool enabled() const { return enabled_; } + size_t width_ = 0; + pad_side side_ = pad_side::left; + bool truncate_ = false; + bool enabled_ = false; +}; + +class SPDLOG_API flag_formatter { +public: + explicit flag_formatter(padding_info padinfo) + : padinfo_(padinfo) {} + flag_formatter() = default; + virtual ~flag_formatter() = default; + virtual void format(const details::log_msg &msg, + const std::tm &tm_time, + memory_buf_t &dest) = 0; + +protected: + padding_info padinfo_; +}; + +} // namespace details + +class SPDLOG_API custom_flag_formatter : public details::flag_formatter { +public: + virtual std::unique_ptr clone() const = 0; + + void set_padding_info(const details::padding_info &padding) { + flag_formatter::padinfo_ = padding; + } +}; + +class SPDLOG_API pattern_formatter final : public formatter { +public: + using custom_flags = std::unordered_map>; + + explicit pattern_formatter(std::string pattern, + pattern_time_type time_type = pattern_time_type::local, + std::string eol = spdlog::details::os::default_eol, + custom_flags custom_user_flags = custom_flags()); + + // use default pattern is not given + explicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, + std::string eol = spdlog::details::os::default_eol); + + pattern_formatter(const pattern_formatter &other) = delete; + pattern_formatter &operator=(const pattern_formatter &other) = delete; + + std::unique_ptr clone() const override; + void format(const details::log_msg &msg, memory_buf_t &dest) override; + + template + pattern_formatter &add_flag(char flag, Args &&...args) { + custom_handlers_[flag] = details::make_unique(std::forward(args)...); + return *this; + } + void set_pattern(std::string pattern); + void need_localtime(bool need = true); + +private: + std::string pattern_; + std::string eol_; + pattern_time_type pattern_time_type_; + bool need_localtime_; + std::tm cached_tm_; + std::chrono::seconds last_log_secs_; + std::vector> formatters_; + custom_flags custom_handlers_; + + std::tm get_time_(const details::log_msg &msg) const; + template + void handle_flag_(char flag, details::padding_info padding); + + // Extract given pad spec (e.g. %8X) + // Advance the given it pass the end of the padding spec found (if any) + // Return padding. + static details::padding_info handle_padspec_(std::string::const_iterator &it, + std::string::const_iterator end); + + void compile_pattern_(const std::string &pattern); +}; +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "pattern_formatter-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/android_sink.h b/lib/spdlog/include/spdlog/sinks/android_sink.h new file mode 100644 index 0000000..c9e1d27 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/android_sink.h @@ -0,0 +1,137 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifdef __ANDROID__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog { +namespace sinks { + +/* + * Android sink + * (logging using __android_log_write or __android_log_buf_write depending on the specified + * BufferID) + */ +template +class android_sink final : public base_sink { +public: + explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) + : tag_(std::move(tag)), + use_raw_msg_(use_raw_msg) {} + +protected: + void sink_it_(const details::log_msg &msg) override { + const android_LogPriority priority = convert_to_android_(msg.level); + memory_buf_t formatted; + if (use_raw_msg_) { + details::fmt_helper::append_string_view(msg.payload, formatted); + } else { + base_sink::formatter_->format(msg, formatted); + } + formatted.push_back('\0'); + const char *msg_output = formatted.data(); + + // See system/core/liblog/logger_write.c for explanation of return value + int ret = android_log(priority, tag_.c_str(), msg_output); + if (ret == -EPERM) { + return; // !__android_log_is_loggable + } + int retry_count = 0; + while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) { + details::os::sleep_for_millis(5); + ret = android_log(priority, tag_.c_str(), msg_output); + retry_count++; + } + + if (ret < 0) { + throw_spdlog_ex("logging to Android failed", ret); + } + } + + void flush_() override {} + +private: + // There might be liblog versions used, that do not support __android_log_buf_write. So we only + // compile and link against + // __android_log_buf_write, if user explicitly provides a non-default log buffer. Otherwise, + // when using the default log buffer, always log via __android_log_write. + template + typename std::enable_if(log_id::LOG_ID_MAIN), int>::type android_log( + int prio, const char *tag, const char *text) { + return __android_log_write(prio, tag, text); + } + + template + typename std::enable_if(log_id::LOG_ID_MAIN), int>::type android_log( + int prio, const char *tag, const char *text) { + return __android_log_buf_write(ID, prio, tag, text); + } + + static android_LogPriority convert_to_android_(spdlog::level::level_enum level) { + switch (level) { + case spdlog::level::trace: + return ANDROID_LOG_VERBOSE; + case spdlog::level::debug: + return ANDROID_LOG_DEBUG; + case spdlog::level::info: + return ANDROID_LOG_INFO; + case spdlog::level::warn: + return ANDROID_LOG_WARN; + case spdlog::level::err: + return ANDROID_LOG_ERROR; + case spdlog::level::critical: + return ANDROID_LOG_FATAL; + default: + return ANDROID_LOG_DEFAULT; + } + } + + std::string tag_; + bool use_raw_msg_; +}; + +using android_sink_mt = android_sink; +using android_sink_st = android_sink; + +template +using android_sink_buf_mt = android_sink; +template +using android_sink_buf_st = android_sink; + +} // namespace sinks + +// Create and register android syslog logger + +template +inline std::shared_ptr android_logger_mt(const std::string &logger_name, + const std::string &tag = "spdlog") { + return Factory::template create(logger_name, tag); +} + +template +inline std::shared_ptr android_logger_st(const std::string &logger_name, + const std::string &tag = "spdlog") { + return Factory::template create(logger_name, tag); +} + +} // namespace spdlog + +#endif // __ANDROID__ diff --git a/lib/spdlog/include/spdlog/sinks/ansicolor_sink-inl.h b/lib/spdlog/include/spdlog/sinks/ansicolor_sink-inl.h new file mode 100644 index 0000000..1d2c544 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/ansicolor_sink-inl.h @@ -0,0 +1,142 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace sinks { + +template +SPDLOG_INLINE ansicolor_sink::ansicolor_sink(FILE *target_file, color_mode mode) + : target_file_(target_file), + mutex_(ConsoleMutex::mutex()), + formatter_(details::make_unique()) + +{ + set_color_mode_(mode); + colors_.at(level::trace) = to_string_(white); + colors_.at(level::debug) = to_string_(cyan); + colors_.at(level::info) = to_string_(green); + colors_.at(level::warn) = to_string_(yellow_bold); + colors_.at(level::err) = to_string_(red_bold); + colors_.at(level::critical) = to_string_(bold_on_red); + colors_.at(level::off) = to_string_(reset); +} + +template +SPDLOG_INLINE void ansicolor_sink::set_color(level::level_enum color_level, + string_view_t color) { + std::lock_guard lock(mutex_); + colors_.at(static_cast(color_level)) = to_string_(color); +} + +template +SPDLOG_INLINE void ansicolor_sink::log(const details::log_msg &msg) { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + std::lock_guard lock(mutex_); + msg.color_range_start = 0; + msg.color_range_end = 0; + memory_buf_t formatted; + formatter_->format(msg, formatted); + if (should_do_colors_ && msg.color_range_end > msg.color_range_start) { + // before color range + print_range_(formatted, 0, msg.color_range_start); + // in color range + print_ccode_(colors_.at(static_cast(msg.level))); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + print_ccode_(reset); + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } else // no color + { + print_range_(formatted, 0, formatted.size()); + } + fflush(target_file_); +} + +template +SPDLOG_INLINE void ansicolor_sink::flush() { + std::lock_guard lock(mutex_); + fflush(target_file_); +} + +template +SPDLOG_INLINE void ansicolor_sink::set_pattern(const std::string &pattern) { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); +} + +template +SPDLOG_INLINE void ansicolor_sink::set_formatter( + std::unique_ptr sink_formatter) { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); +} + +template +SPDLOG_INLINE bool ansicolor_sink::should_color() const { + return should_do_colors_; +} + +template +SPDLOG_INLINE void ansicolor_sink::set_color_mode(color_mode mode) { + std::lock_guard lock(mutex_); + set_color_mode_(mode); +} + +template +SPDLOG_INLINE void ansicolor_sink::set_color_mode_(color_mode mode) { + switch (mode) { + case color_mode::always: + should_do_colors_ = true; + return; + case color_mode::automatic: + should_do_colors_ = + details::os::in_terminal(target_file_) && details::os::is_color_terminal(); + return; + case color_mode::never: + should_do_colors_ = false; + return; + default: + should_do_colors_ = false; + } +} + +template +SPDLOG_INLINE void ansicolor_sink::print_ccode_( + const string_view_t &color_code) const { + details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_); +} + +template +SPDLOG_INLINE void ansicolor_sink::print_range_(const memory_buf_t &formatted, + size_t start, + size_t end) const { + details::os::fwrite_bytes(formatted.data() + start, end - start, target_file_); +} + +template +SPDLOG_INLINE std::string ansicolor_sink::to_string_(const string_view_t &sv) { + return std::string(sv.data(), sv.size()); +} + +// ansicolor_stdout_sink +template +SPDLOG_INLINE ansicolor_stdout_sink::ansicolor_stdout_sink(color_mode mode) + : ansicolor_sink(stdout, mode) {} + +// ansicolor_stderr_sink +template +SPDLOG_INLINE ansicolor_stderr_sink::ansicolor_stderr_sink(color_mode mode) + : ansicolor_sink(stderr, mode) {} + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/ansicolor_sink.h b/lib/spdlog/include/spdlog/sinks/ansicolor_sink.h new file mode 100644 index 0000000..542888f --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/ansicolor_sink.h @@ -0,0 +1,118 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +/** + * This sink prefixes the output with an ANSI escape sequence color code + * depending on the severity + * of the message. + * If no color terminal detected, omit the escape codes. + */ + +template +class ansicolor_sink : public sink { +public: + using mutex_t = typename ConsoleMutex::mutex_t; + ansicolor_sink(FILE *target_file, color_mode mode); + ~ansicolor_sink() override = default; + + ansicolor_sink(const ansicolor_sink &other) = delete; + ansicolor_sink(ansicolor_sink &&other) = delete; + + ansicolor_sink &operator=(const ansicolor_sink &other) = delete; + ansicolor_sink &operator=(ansicolor_sink &&other) = delete; + + void set_color(level::level_enum color_level, string_view_t color); + void set_color_mode(color_mode mode); + bool should_color() const; + + void log(const details::log_msg &msg) override; + void flush() override; + void set_pattern(const std::string &pattern) override; + void set_formatter(std::unique_ptr sink_formatter) override; + + // Formatting codes + const string_view_t reset = "\033[m"; + const string_view_t bold = "\033[1m"; + const string_view_t dark = "\033[2m"; + const string_view_t underline = "\033[4m"; + const string_view_t blink = "\033[5m"; + const string_view_t reverse = "\033[7m"; + const string_view_t concealed = "\033[8m"; + const string_view_t clear_line = "\033[K"; + + // Foreground colors + const string_view_t black = "\033[30m"; + const string_view_t red = "\033[31m"; + const string_view_t green = "\033[32m"; + const string_view_t yellow = "\033[33m"; + const string_view_t blue = "\033[34m"; + const string_view_t magenta = "\033[35m"; + const string_view_t cyan = "\033[36m"; + const string_view_t white = "\033[37m"; + + /// Background colors + const string_view_t on_black = "\033[40m"; + const string_view_t on_red = "\033[41m"; + const string_view_t on_green = "\033[42m"; + const string_view_t on_yellow = "\033[43m"; + const string_view_t on_blue = "\033[44m"; + const string_view_t on_magenta = "\033[45m"; + const string_view_t on_cyan = "\033[46m"; + const string_view_t on_white = "\033[47m"; + + /// Bold colors + const string_view_t yellow_bold = "\033[33m\033[1m"; + const string_view_t red_bold = "\033[31m\033[1m"; + const string_view_t bold_on_red = "\033[1m\033[41m"; + +protected: + FILE *target_file_; + +private: + mutex_t &mutex_; + bool should_do_colors_; + std::unique_ptr formatter_; + std::array colors_; + void set_color_mode_(color_mode mode); + void print_ccode_(const string_view_t &color_code) const; + void print_range_(const memory_buf_t &formatted, size_t start, size_t end) const; + static std::string to_string_(const string_view_t &sv); +}; + +template +class ansicolor_stdout_sink : public ansicolor_sink { +public: + explicit ansicolor_stdout_sink(color_mode mode = color_mode::automatic); +}; + +template +class ansicolor_stderr_sink : public ansicolor_sink { +public: + explicit ansicolor_stderr_sink(color_mode mode = color_mode::automatic); +}; + +using ansicolor_stdout_sink_mt = ansicolor_stdout_sink; +using ansicolor_stdout_sink_st = ansicolor_stdout_sink; + +using ansicolor_stderr_sink_mt = ansicolor_stderr_sink; +using ansicolor_stderr_sink_st = ansicolor_stderr_sink; + +} // namespace sinks +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "ansicolor_sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/base_sink-inl.h b/lib/spdlog/include/spdlog/sinks/base_sink-inl.h new file mode 100644 index 0000000..4c3625e --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/base_sink-inl.h @@ -0,0 +1,59 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +#include +#include + +template +SPDLOG_INLINE spdlog::sinks::base_sink::base_sink() + : formatter_{details::make_unique()} {} + +template +SPDLOG_INLINE spdlog::sinks::base_sink::base_sink( + std::unique_ptr formatter) + : formatter_{std::move(formatter)} {} + +template +void SPDLOG_INLINE spdlog::sinks::base_sink::log(const details::log_msg &msg) { + std::lock_guard lock(mutex_); + sink_it_(msg); +} + +template +void SPDLOG_INLINE spdlog::sinks::base_sink::flush() { + std::lock_guard lock(mutex_); + flush_(); +} + +template +void SPDLOG_INLINE spdlog::sinks::base_sink::set_pattern(const std::string &pattern) { + std::lock_guard lock(mutex_); + set_pattern_(pattern); +} + +template +void SPDLOG_INLINE +spdlog::sinks::base_sink::set_formatter(std::unique_ptr sink_formatter) { + std::lock_guard lock(mutex_); + set_formatter_(std::move(sink_formatter)); +} + +template +void SPDLOG_INLINE spdlog::sinks::base_sink::set_pattern_(const std::string &pattern) { + set_formatter_(details::make_unique(pattern)); +} + +template +void SPDLOG_INLINE +spdlog::sinks::base_sink::set_formatter_(std::unique_ptr sink_formatter) { + formatter_ = std::move(sink_formatter); +} diff --git a/lib/spdlog/include/spdlog/sinks/base_sink.h b/lib/spdlog/include/spdlog/sinks/base_sink.h new file mode 100644 index 0000000..c4fb4fc --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/base_sink.h @@ -0,0 +1,51 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once +// +// base sink templated over a mutex (either dummy or real) +// concrete implementation should override the sink_it_() and flush_() methods. +// locking is taken care of in this class - no locking needed by the +// implementers.. +// + +#include +#include +#include + +namespace spdlog { +namespace sinks { +template +class SPDLOG_API base_sink : public sink { +public: + base_sink(); + explicit base_sink(std::unique_ptr formatter); + ~base_sink() override = default; + + base_sink(const base_sink &) = delete; + base_sink(base_sink &&) = delete; + + base_sink &operator=(const base_sink &) = delete; + base_sink &operator=(base_sink &&) = delete; + + void log(const details::log_msg &msg) final override; + void flush() final override; + void set_pattern(const std::string &pattern) final override; + void set_formatter(std::unique_ptr sink_formatter) final override; + +protected: + // sink formatter + std::unique_ptr formatter_; + Mutex mutex_; + + virtual void sink_it_(const details::log_msg &msg) = 0; + virtual void flush_() = 0; + virtual void set_pattern_(const std::string &pattern); + virtual void set_formatter_(std::unique_ptr sink_formatter); +}; +} // namespace sinks +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "base_sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/basic_file_sink-inl.h b/lib/spdlog/include/spdlog/sinks/basic_file_sink-inl.h new file mode 100644 index 0000000..8aec33c --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/basic_file_sink-inl.h @@ -0,0 +1,48 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { +namespace sinks { + +template +SPDLOG_INLINE basic_file_sink::basic_file_sink(const filename_t &filename, + bool truncate, + const file_event_handlers &event_handlers) + : file_helper_{event_handlers} { + file_helper_.open(filename, truncate); +} + +template +SPDLOG_INLINE const filename_t &basic_file_sink::filename() const { + return file_helper_.filename(); +} + +template +SPDLOG_INLINE void basic_file_sink::truncate() { + std::lock_guard lock(base_sink::mutex_); + file_helper_.reopen(true); +} + +template +SPDLOG_INLINE void basic_file_sink::sink_it_(const details::log_msg &msg) { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); +} + +template +SPDLOG_INLINE void basic_file_sink::flush_() { + file_helper_.flush(); +} + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/basic_file_sink.h b/lib/spdlog/include/spdlog/sinks/basic_file_sink.h new file mode 100644 index 0000000..23ac6c1 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/basic_file_sink.h @@ -0,0 +1,66 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Trivial file sink with single file as target + */ +template +class basic_file_sink final : public base_sink { +public: + explicit basic_file_sink(const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}); + const filename_t &filename() const; + void truncate(); + +protected: + void sink_it_(const details::log_msg &msg) override; + void flush_() override; + +private: + details::file_helper file_helper_; +}; + +using basic_file_sink_mt = basic_file_sink; +using basic_file_sink_st = basic_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr basic_logger_mt(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + event_handlers); +} + +template +inline std::shared_ptr basic_logger_st(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + event_handlers); +} + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "basic_file_sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/callback_sink.h b/lib/spdlog/include/spdlog/sinks/callback_sink.h new file mode 100644 index 0000000..8f0c8d4 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/callback_sink.h @@ -0,0 +1,56 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include + +namespace spdlog { + +// callbacks type +typedef std::function custom_log_callback; + +namespace sinks { +/* + * Trivial callback sink, gets a callback function and calls it on each log + */ +template +class callback_sink final : public base_sink { +public: + explicit callback_sink(const custom_log_callback &callback) + : callback_{callback} {} + +protected: + void sink_it_(const details::log_msg &msg) override { callback_(msg); } + void flush_() override {} + +private: + custom_log_callback callback_; +}; + +using callback_sink_mt = callback_sink; +using callback_sink_st = callback_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr callback_logger_mt(const std::string &logger_name, + const custom_log_callback &callback) { + return Factory::template create(logger_name, callback); +} + +template +inline std::shared_ptr callback_logger_st(const std::string &logger_name, + const custom_log_callback &callback) { + return Factory::template create(logger_name, callback); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/daily_file_sink.h b/lib/spdlog/include/spdlog/sinks/daily_file_sink.h new file mode 100644 index 0000000..fa2582d --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/daily_file_sink.h @@ -0,0 +1,254 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct daily_filename_calculator { + // Create filename for the form basename.YYYY-MM-DD + 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_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}")), + basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, + ext); + } +}; + +/* + * Generator of daily log file names with strftime format. + * Usages: + * auto sink = + * std::make_shared("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 &file_path, const tm &now_tm) { +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + std::wstringstream stream; +#else + std::stringstream stream; +#endif + stream << std::put_time(&now_tm, file_path.c_str()); + return stream.str(); + } +}; + +/* + * Rotating file sink based on date. + * If truncate != false , the created file will be truncated. + * If max_files > 0, retain only the last max_files and delete previous. + * Note that old log files from previous executions will not be deleted by this class, + * rotation and deletion is only applied while the program is running. + */ +template +class daily_file_sink final : public base_sink { +public: + // create daily file sink which rotates on given time + daily_file_sink(filename_t base_filename, + int rotation_hour, + int rotation_minute, + bool truncate = false, + uint16_t max_files = 0, + const file_event_handlers &event_handlers = {}) + : base_filename_(std::move(base_filename)), + rotation_h_(rotation_hour), + rotation_m_(rotation_minute), + file_helper_{event_handlers}, + truncate_(truncate), + max_files_(max_files), + filenames_q_() { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || + rotation_minute > 59) { + throw_spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + } + + auto now = log_clock::now(); + const auto new_filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); + file_helper_.open(new_filename, truncate_); + rotation_tp_ = next_rotation_tp_(); + + if (max_files_ > 0) { + init_filenames_q_(); + } + } + + filename_t filename() { + std::lock_guard lock(base_sink::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) { + const auto new_filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); + file_helper_.open(new_filename, truncate_); + rotation_tp_ = next_rotation_tp_(); + } + memory_buf_t formatted; + base_sink::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(static_cast(max_files_)); + std::vector filenames; + auto now = log_clock::now(); + while (filenames.size() < max_files_) { + const auto new_filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); + if (!path_exists(new_filename)) { + break; + } + filenames.emplace_back(new_filename); + now -= std::chrono::hours(24); + } + 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_hour = rotation_h_; + date.tm_min = rotation_m_; + 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(24)}; + } + + // 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)); + throw_spdlog_ex("Failed removing daily file " + filename_to_str(old_filename), + errno); + } + } + filenames_q_.push_back(std::move(current_file)); + } + + filename_t base_filename_; + int rotation_h_; + int rotation_m_; + log_clock::time_point rotation_tp_; + details::file_helper file_helper_; + bool truncate_; + uint16_t max_files_; + details::circular_q filenames_q_; +}; + +using daily_file_sink_mt = daily_file_sink; +using daily_file_sink_st = daily_file_sink; +using daily_file_format_sink_mt = daily_file_sink; +using daily_file_format_sink_st = + daily_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr daily_logger_mt(const std::string &logger_name, + const filename_t &filename, + int hour = 0, + int minute = 0, + bool truncate = false, + uint16_t max_files = 0, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, hour, minute, + truncate, max_files, event_handlers); +} + +template +inline std::shared_ptr 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, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, hour, minute, truncate, max_files, event_handlers); +} + +template +inline std::shared_ptr 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 file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, hour, minute, + truncate, max_files, event_handlers); +} + +template +inline std::shared_ptr 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, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, hour, minute, truncate, max_files, event_handlers); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/dist_sink.h b/lib/spdlog/include/spdlog/sinks/dist_sink.h new file mode 100644 index 0000000..b143e58 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/dist_sink.h @@ -0,0 +1,81 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include "base_sink.h" +#include +#include +#include + +#include +#include +#include +#include + +// Distribution sink (mux). Stores a vector of sinks which get called when log +// is called + +namespace spdlog { +namespace sinks { + +template +class dist_sink : public base_sink { +public: + dist_sink() = default; + explicit dist_sink(std::vector> sinks) + : sinks_(std::move(sinks)) {} + + dist_sink(const dist_sink &) = delete; + dist_sink &operator=(const dist_sink &) = delete; + + void add_sink(std::shared_ptr sub_sink) { + std::lock_guard lock(base_sink::mutex_); + sinks_.push_back(sub_sink); + } + + void remove_sink(std::shared_ptr sub_sink) { + std::lock_guard lock(base_sink::mutex_); + sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sub_sink), sinks_.end()); + } + + void set_sinks(std::vector> sinks) { + std::lock_guard lock(base_sink::mutex_); + sinks_ = std::move(sinks); + } + + std::vector> &sinks() { return sinks_; } + +protected: + void sink_it_(const details::log_msg &msg) override { + for (auto &sub_sink : sinks_) { + if (sub_sink->should_log(msg.level)) { + sub_sink->log(msg); + } + } + } + + void flush_() override { + for (auto &sub_sink : sinks_) { + sub_sink->flush(); + } + } + + void set_pattern_(const std::string &pattern) override { + set_formatter_(details::make_unique(pattern)); + } + + void set_formatter_(std::unique_ptr sink_formatter) override { + base_sink::formatter_ = std::move(sink_formatter); + for (auto &sub_sink : sinks_) { + sub_sink->set_formatter(base_sink::formatter_->clone()); + } + } + std::vector> sinks_; +}; + +using dist_sink_mt = dist_sink; +using dist_sink_st = dist_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/dup_filter_sink.h b/lib/spdlog/include/spdlog/sinks/dup_filter_sink.h new file mode 100644 index 0000000..1071e98 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/dup_filter_sink.h @@ -0,0 +1,91 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include "dist_sink.h" +#include +#include + +#include +#include +#include +#include + +// Duplicate message removal sink. +// Skip the message if previous one is identical and less than "max_skip_duration" have passed +// +// Example: +// +// #include +// +// int main() { +// auto dup_filter = std::make_shared(std::chrono::seconds(5), +// level::info); dup_filter->add_sink(std::make_shared()); +// spdlog::logger l("logger", dup_filter); +// l.info("Hello"); +// l.info("Hello"); +// l.info("Hello"); +// l.info("Different Hello"); +// } +// +// Will produce: +// [2019-06-25 17:50:56.511] [logger] [info] Hello +// [2019-06-25 17:50:56.512] [logger] [info] Skipped 3 duplicate messages.. +// [2019-06-25 17:50:56.512] [logger] [info] Different Hello + +namespace spdlog { +namespace sinks { +template +class dup_filter_sink : public dist_sink { +public: + template + explicit dup_filter_sink(std::chrono::duration max_skip_duration) + : max_skip_duration_{max_skip_duration} {} + +protected: + std::chrono::microseconds max_skip_duration_; + log_clock::time_point last_msg_time_; + std::string last_msg_payload_; + size_t skip_counter_ = 0; + level::level_enum skipped_msg_log_level_ = spdlog::level::level_enum::off; + + void sink_it_(const details::log_msg &msg) override { + bool filtered = filter_(msg); + if (!filtered) { + skip_counter_ += 1; + skipped_msg_log_level_ = msg.level; + return; + } + + // log the "skipped.." message + if (skip_counter_ > 0) { + char buf[64]; + auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", + static_cast(skip_counter_)); + if (msg_size > 0 && static_cast(msg_size) < sizeof(buf)) { + details::log_msg skipped_msg{msg.source, msg.logger_name, skipped_msg_log_level_, + string_view_t{buf, static_cast(msg_size)}}; + dist_sink::sink_it_(skipped_msg); + } + } + + // log current message + dist_sink::sink_it_(msg); + last_msg_time_ = msg.time; + skip_counter_ = 0; + last_msg_payload_.assign(msg.payload.data(), msg.payload.data() + msg.payload.size()); + } + + // return whether the log msg should be displayed (true) or skipped (false) + bool filter_(const details::log_msg &msg) const { + const auto filter_duration = msg.time - last_msg_time_; + return (filter_duration > max_skip_duration_) || (msg.payload != last_msg_payload_); + } +}; + +using dup_filter_sink_mt = dup_filter_sink; +using dup_filter_sink_st = dup_filter_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/hourly_file_sink.h b/lib/spdlog/include/spdlog/sinks/hourly_file_sink.h new file mode 100644 index 0000000..3e61872 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/hourly_file_sink.h @@ -0,0 +1,193 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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_lib::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. + * Note that old log files from previous executions will not be deleted by this class, + * rotation and deletion is only applied while the program is running. + */ +template +class hourly_file_sink final : public base_sink { +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, + const file_event_handlers &event_handlers = {}) + : base_filename_(std::move(base_filename)), + file_helper_{event_handlers}, + 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_); + remove_init_file_ = file_helper_.size() == 0; + rotation_tp_ = next_rotation_tp_(); + + if (max_files_ > 0) { + init_filenames_q_(); + } + } + + filename_t filename() { + std::lock_guard lock(base_sink::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) { + if (remove_init_file_) { + file_helper_.close(); + details::os::remove(file_helper_.filename()); + } + auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); + file_helper_.open(filename, truncate_); + rotation_tp_ = next_rotation_tp_(); + } + remove_init_file_ = false; + memory_buf_t formatted; + base_sink::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(static_cast(max_files_)); + std::vector 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 filenames_q_; + bool remove_init_file_; +}; + +using hourly_file_sink_mt = hourly_file_sink; +using hourly_file_sink_st = hourly_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr hourly_logger_mt(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + uint16_t max_files = 0, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + max_files, event_handlers); +} + +template +inline std::shared_ptr hourly_logger_st(const std::string &logger_name, + const filename_t &filename, + bool truncate = false, + uint16_t max_files = 0, + const file_event_handlers &event_handlers = {}) { + return Factory::template create(logger_name, filename, truncate, + max_files, event_handlers); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/kafka_sink.h b/lib/spdlog/include/spdlog/sinks/kafka_sink.h new file mode 100644 index 0000000..91e9878 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/kafka_sink.h @@ -0,0 +1,119 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +// +// Custom sink for kafka +// Building and using requires librdkafka library. +// For building librdkafka library check the url below +// https://github.com/confluentinc/librdkafka +// + +#include "spdlog/async.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/synchronous_factory.h" +#include "spdlog/sinks/base_sink.h" +#include +#include + +// kafka header +#include + +namespace spdlog { +namespace sinks { + +struct kafka_sink_config { + std::string server_addr; + std::string produce_topic; + int32_t flush_timeout_ms = 1000; + + kafka_sink_config(std::string addr, std::string topic, int flush_timeout_ms = 1000) + : server_addr{std::move(addr)}, + produce_topic{std::move(topic)}, + flush_timeout_ms(flush_timeout_ms) {} +}; + +template +class kafka_sink : public base_sink { +public: + kafka_sink(kafka_sink_config config) + : config_{std::move(config)} { + try { + std::string errstr; + conf_.reset(RdKafka::Conf::create(RdKafka::Conf::CONF_GLOBAL)); + RdKafka::Conf::ConfResult confRes = + conf_->set("bootstrap.servers", config_.server_addr, errstr); + if (confRes != RdKafka::Conf::CONF_OK) { + throw_spdlog_ex( + fmt_lib::format("conf set bootstrap.servers failed err:{}", errstr)); + } + + tconf_.reset(RdKafka::Conf::create(RdKafka::Conf::CONF_TOPIC)); + if (tconf_ == nullptr) { + throw_spdlog_ex(fmt_lib::format("create topic config failed")); + } + + producer_.reset(RdKafka::Producer::create(conf_.get(), errstr)); + if (producer_ == nullptr) { + throw_spdlog_ex(fmt_lib::format("create producer failed err:{}", errstr)); + } + topic_.reset(RdKafka::Topic::create(producer_.get(), config_.produce_topic, + tconf_.get(), errstr)); + if (topic_ == nullptr) { + throw_spdlog_ex(fmt_lib::format("create topic failed err:{}", errstr)); + } + } catch (const std::exception &e) { + throw_spdlog_ex(fmt_lib::format("error create kafka instance: {}", e.what())); + } + } + + ~kafka_sink() { producer_->flush(config_.flush_timeout_ms); } + +protected: + void sink_it_(const details::log_msg &msg) override { + producer_->produce(topic_.get(), 0, RdKafka::Producer::RK_MSG_COPY, + (void *)msg.payload.data(), msg.payload.size(), NULL, NULL); + } + + void flush_() override { producer_->flush(config_.flush_timeout_ms); } + +private: + kafka_sink_config config_; + std::unique_ptr producer_ = nullptr; + std::unique_ptr conf_ = nullptr; + std::unique_ptr tconf_ = nullptr; + std::unique_ptr topic_ = nullptr; +}; + +using kafka_sink_mt = kafka_sink; +using kafka_sink_st = kafka_sink; + +} // namespace sinks + +template +inline std::shared_ptr kafka_logger_mt(const std::string &logger_name, + spdlog::sinks::kafka_sink_config config) { + return Factory::template create(logger_name, config); +} + +template +inline std::shared_ptr kafka_logger_st(const std::string &logger_name, + spdlog::sinks::kafka_sink_config config) { + return Factory::template create(logger_name, config); +} + +template +inline std::shared_ptr kafka_logger_async_mt( + std::string logger_name, spdlog::sinks::kafka_sink_config config) { + return Factory::template create(logger_name, config); +} + +template +inline std::shared_ptr kafka_logger_async_st( + std::string logger_name, spdlog::sinks::kafka_sink_config config) { + return Factory::template create(logger_name, config); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/mongo_sink.h b/lib/spdlog/include/spdlog/sinks/mongo_sink.h new file mode 100644 index 0000000..c5b38ab --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/mongo_sink.h @@ -0,0 +1,108 @@ +// 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 + +#include +#include +#include + +#include +#include +#include + +namespace spdlog { +namespace sinks { +template +class mongo_sink : public base_sink { +public: + mongo_sink(const std::string &db_name, + const std::string &collection_name, + const std::string &uri = "mongodb://localhost:27017") try + : mongo_sink(std::make_shared(), db_name, collection_name, uri) { + } catch (const std::exception &e) { + throw_spdlog_ex(fmt_lib::format("Error opening database: {}", e.what())); + } + + mongo_sink(std::shared_ptr instance, + const std::string &db_name, + const std::string &collection_name, + const std::string &uri = "mongodb://localhost:27017") + : instance_(std::move(instance)), + db_name_(db_name), + coll_name_(collection_name) { + try { + client_ = spdlog::details::make_unique(mongocxx::uri{uri}); + } catch (const std::exception &e) { + throw_spdlog_ex(fmt_lib::format("Error opening database: {}", e.what())); + } + } + + ~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() << "level_num" + << msg.level << "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(msg.thread_id) << finalize; + client_->database(db_name_).collection(coll_name_).insert_one(doc.view()); + } + } + + void flush_() override {} + +private: + std::shared_ptr instance_; + std::string db_name_; + std::string coll_name_; + std::unique_ptr client_ = nullptr; +}; + +#include "spdlog/details/null_mutex.h" +#include +using mongo_sink_mt = mongo_sink; +using mongo_sink_st = mongo_sink; + +} // namespace sinks + +template +inline std::shared_ptr 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(logger_name, db_name, collection_name, + uri); +} + +template +inline std::shared_ptr 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(logger_name, db_name, collection_name, + uri); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/msvc_sink.h b/lib/spdlog/include/spdlog/sinks/msvc_sink.h new file mode 100644 index 0000000..1fe9892 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/msvc_sink.h @@ -0,0 +1,68 @@ +// Copyright(c) 2016 Alexander Dalshov & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#if defined(_WIN32) + +#include +#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +#include +#endif +#include + +#include +#include + +// Avoid including windows.h (https://stackoverflow.com/a/30741042) +#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringW(const wchar_t *lpOutputString); +#else +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char *lpOutputString); +#endif +extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + +namespace spdlog { +namespace sinks { +/* + * MSVC sink (logging using OutputDebugStringA) + */ +template +class msvc_sink : public base_sink { +public: + msvc_sink() = default; + msvc_sink(bool check_debugger_present) + : check_debugger_present_{check_debugger_present} {} + +protected: + void sink_it_(const details::log_msg &msg) override { + if (check_debugger_present_ && !IsDebuggerPresent()) { + return; + } + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + formatted.push_back('\0'); // add a null terminator for OutputDebugString +#if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) + wmemory_buf_t wformatted; + details::os::utf8_to_wstrbuf(string_view_t(formatted.data(), formatted.size()), wformatted); + OutputDebugStringW(wformatted.data()); +#else + OutputDebugStringA(formatted.data()); +#endif + } + + void flush_() override {} + + bool check_debugger_present_ = true; +}; + +using msvc_sink_mt = msvc_sink; +using msvc_sink_st = msvc_sink; + +using windebug_sink_mt = msvc_sink_mt; +using windebug_sink_st = msvc_sink_st; + +} // namespace sinks +} // namespace spdlog + +#endif diff --git a/lib/spdlog/include/spdlog/sinks/null_sink.h b/lib/spdlog/include/spdlog/sinks/null_sink.h new file mode 100644 index 0000000..74530b5 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/null_sink.h @@ -0,0 +1,41 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include + +namespace spdlog { +namespace sinks { + +template +class null_sink final : public base_sink { +protected: + void sink_it_(const details::log_msg &) override {} + void flush_() override {} +}; + +using null_sink_mt = null_sink; +using null_sink_st = null_sink; + +} // namespace sinks + +template +inline std::shared_ptr null_logger_mt(const std::string &logger_name) { + auto null_logger = Factory::template create(logger_name); + null_logger->set_level(level::off); + return null_logger; +} + +template +inline std::shared_ptr null_logger_st(const std::string &logger_name) { + auto null_logger = Factory::template create(logger_name); + null_logger->set_level(level::off); + return null_logger; +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/ostream_sink.h b/lib/spdlog/include/spdlog/sinks/ostream_sink.h new file mode 100644 index 0000000..6af9dd0 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/ostream_sink.h @@ -0,0 +1,43 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +#include +#include + +namespace spdlog { +namespace sinks { +template +class ostream_sink final : public base_sink { +public: + explicit ostream_sink(std::ostream &os, bool force_flush = false) + : ostream_(os), + force_flush_(force_flush) {} + ostream_sink(const ostream_sink &) = delete; + ostream_sink &operator=(const ostream_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + ostream_.write(formatted.data(), static_cast(formatted.size())); + if (force_flush_) { + ostream_.flush(); + } + } + + void flush_() override { ostream_.flush(); } + + std::ostream &ostream_; + bool force_flush_; +}; + +using ostream_sink_mt = ostream_sink; +using ostream_sink_st = ostream_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/qt_sinks.h b/lib/spdlog/include/spdlog/sinks/qt_sinks.h new file mode 100644 index 0000000..ce74f88 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/qt_sinks.h @@ -0,0 +1,308 @@ +// 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. +// +// Warning: the qt_sink won't be notified if the target widget is destroyed. +// If the widget's lifetime can be shorter than the logger's one, you should provide some permanent +// QObject, and then use a standard signal/slot. +// + +#include "spdlog/common.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/synchronous_factory.h" +#include "spdlog/sinks/base_sink.h" +#include + +#include +#include + +// +// qt_sink class +// +namespace spdlog { +namespace sinks { +template +class qt_sink : public base_sink { +public: + qt_sink(QObject *qt_object, std::string meta_method) + : qt_object_(qt_object), + meta_method_(std::move(meta_method)) { + if (!qt_object_) { + throw_spdlog_ex("qt_sink: qt_object is null"); + } + } + + ~qt_sink() { flush_(); } + +protected: + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + const string_view_t str = string_view_t(formatted.data(), formatted.size()); + QMetaObject::invokeMethod( + qt_object_, meta_method_.c_str(), Qt::AutoConnection, + Q_ARG(QString, QString::fromUtf8(str.data(), static_cast(str.size())).trimmed())); + } + + void flush_() override {} + +private: + QObject *qt_object_ = nullptr; + std::string meta_method_; +}; + +// Qt color sink to QTextEdit. +// Color location is determined by the sink log pattern like in the rest of spdlog sinks. +// Colors can be modified if needed using sink->set_color(level, qtTextCharFormat). +// max_lines is the maximum number of lines that the sink will hold before removing the oldest +// lines. By default, only ascii (latin1) is supported by this sink. Set is_utf8 to true if utf8 +// support is needed. +template +class qt_color_sink : public base_sink { +public: + qt_color_sink(QTextEdit *qt_text_edit, + int max_lines, + bool dark_colors = false, + bool is_utf8 = false) + : qt_text_edit_(qt_text_edit), + max_lines_(max_lines), + is_utf8_(is_utf8) { + if (!qt_text_edit_) { + throw_spdlog_ex("qt_color_text_sink: text_edit is null"); + } + + default_color_ = qt_text_edit_->currentCharFormat(); + // set colors + QTextCharFormat format; + // trace + format.setForeground(dark_colors ? Qt::darkGray : Qt::gray); + colors_.at(level::trace) = format; + // debug + format.setForeground(dark_colors ? Qt::darkCyan : Qt::cyan); + colors_.at(level::debug) = format; + // info + format.setForeground(dark_colors ? Qt::darkGreen : Qt::green); + colors_.at(level::info) = format; + // warn + format.setForeground(dark_colors ? Qt::darkYellow : Qt::yellow); + colors_.at(level::warn) = format; + // err + format.setForeground(Qt::red); + colors_.at(level::err) = format; + // critical + format.setForeground(Qt::white); + format.setBackground(Qt::red); + colors_.at(level::critical) = format; + } + + ~qt_color_sink() { flush_(); } + + void set_default_color(QTextCharFormat format) { + // std::lock_guard lock(base_sink::mutex_); + default_color_ = format; + } + + void set_level_color(level::level_enum color_level, QTextCharFormat format) { + // std::lock_guard lock(base_sink::mutex_); + colors_.at(static_cast(color_level)) = format; + } + + QTextCharFormat &get_level_color(level::level_enum color_level) { + std::lock_guard lock(base_sink::mutex_); + return colors_.at(static_cast(color_level)); + } + + QTextCharFormat &get_default_color() { + std::lock_guard lock(base_sink::mutex_); + return default_color_; + } + +protected: + struct invoke_params { + invoke_params(int max_lines, + QTextEdit *q_text_edit, + QString payload, + QTextCharFormat default_color, + QTextCharFormat level_color, + int color_range_start, + int color_range_end) + : max_lines(max_lines), + q_text_edit(q_text_edit), + payload(std::move(payload)), + default_color(default_color), + level_color(level_color), + color_range_start(color_range_start), + color_range_end(color_range_end) {} + int max_lines; + QTextEdit *q_text_edit; + QString payload; + QTextCharFormat default_color; + QTextCharFormat level_color; + int color_range_start; + int color_range_end; + }; + + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + + const string_view_t str = string_view_t(formatted.data(), formatted.size()); + // apply the color to the color range in the formatted message. + QString payload; + int color_range_start = static_cast(msg.color_range_start); + int color_range_end = static_cast(msg.color_range_end); + if (is_utf8_) { + payload = QString::fromUtf8(str.data(), static_cast(str.size())); + // convert color ranges from byte index to character index. + if (msg.color_range_start < msg.color_range_end) { + color_range_start = + QString::fromUtf8(str.data(), static_cast(msg.color_range_start)) + .size(); + color_range_end = + QString::fromUtf8(str.data(), static_cast(msg.color_range_end)) + .size(); + } + } else { + payload = QString::fromLatin1(str.data(), static_cast(str.size())); + } + + invoke_params params{max_lines_, // max lines + qt_text_edit_, // text edit to append to + std::move(payload), // text to append + default_color_, // default color + colors_.at(static_cast(msg.level)), // color to apply + color_range_start, // color range start + color_range_end}; // color range end + + QMetaObject::invokeMethod( + qt_text_edit_, [params]() { invoke_method_(params); }, Qt::AutoConnection); + } + + void flush_() override {} + + // Add colored text to the text edit widget. This method is invoked in the GUI thread. + // It is a static method to ensure that it is handled correctly even if the sink is destroyed + // prematurely before it is invoked. + + static void invoke_method_(invoke_params params) { + auto *document = params.q_text_edit->document(); + QTextCursor cursor(document); + + // remove first blocks if number of blocks exceeds max_lines + while (document->blockCount() > params.max_lines) { + cursor.select(QTextCursor::BlockUnderCursor); + cursor.removeSelectedText(); + cursor.deleteChar(); // delete the newline after the block + } + + cursor.movePosition(QTextCursor::End); + cursor.setCharFormat(params.default_color); + + // if color range not specified or not not valid, just append the text with default color + if (params.color_range_end <= params.color_range_start) { + cursor.insertText(params.payload); + return; + } + + // insert the text before the color range + cursor.insertText(params.payload.left(params.color_range_start)); + + // insert the colorized text + cursor.setCharFormat(params.level_color); + cursor.insertText(params.payload.mid(params.color_range_start, + params.color_range_end - params.color_range_start)); + + // insert the text after the color range with default format + cursor.setCharFormat(params.default_color); + cursor.insertText(params.payload.mid(params.color_range_end)); + } + + QTextEdit *qt_text_edit_; + int max_lines_; + bool is_utf8_; + QTextCharFormat default_color_; + std::array colors_; +}; + +#include "spdlog/details/null_mutex.h" +#include + +using qt_sink_mt = qt_sink; +using qt_sink_st = qt_sink; +using qt_color_sink_mt = qt_color_sink; +using qt_color_sink_st = qt_color_sink; +} // namespace sinks + +// +// Factory functions +// + +// log to QTextEdit +template +inline std::shared_ptr qt_logger_mt(const std::string &logger_name, + QTextEdit *qt_object, + const std::string &meta_method = "append") { + return Factory::template create(logger_name, qt_object, meta_method); +} + +template +inline std::shared_ptr qt_logger_st(const std::string &logger_name, + QTextEdit *qt_object, + const std::string &meta_method = "append") { + return Factory::template create(logger_name, qt_object, meta_method); +} + +// log to QPlainTextEdit +template +inline std::shared_ptr qt_logger_mt(const std::string &logger_name, + QPlainTextEdit *qt_object, + const std::string &meta_method = "appendPlainText") { + return Factory::template create(logger_name, qt_object, meta_method); +} + +template +inline std::shared_ptr qt_logger_st(const std::string &logger_name, + QPlainTextEdit *qt_object, + const std::string &meta_method = "appendPlainText") { + return Factory::template create(logger_name, qt_object, meta_method); +} +// log to QObject +template +inline std::shared_ptr qt_logger_mt(const std::string &logger_name, + QObject *qt_object, + const std::string &meta_method) { + return Factory::template create(logger_name, qt_object, meta_method); +} + +template +inline std::shared_ptr qt_logger_st(const std::string &logger_name, + QObject *qt_object, + const std::string &meta_method) { + return Factory::template create(logger_name, qt_object, meta_method); +} + +// log to QTextEdit with colorized output +template +inline std::shared_ptr qt_color_logger_mt(const std::string &logger_name, + QTextEdit *qt_text_edit, + int max_lines, + bool is_utf8 = false) { + return Factory::template create(logger_name, qt_text_edit, max_lines, + false, is_utf8); +} + +template +inline std::shared_ptr qt_color_logger_st(const std::string &logger_name, + QTextEdit *qt_text_edit, + int max_lines, + bool is_utf8 = false) { + return Factory::template create(logger_name, qt_text_edit, max_lines, + false, is_utf8); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/ringbuffer_sink.h b/lib/spdlog/include/spdlog/sinks/ringbuffer_sink.h new file mode 100644 index 0000000..bcdf0ff --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/ringbuffer_sink.h @@ -0,0 +1,71 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include "spdlog/details/circular_q.h" +#include "spdlog/details/log_msg_buffer.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Ring buffer sink + */ +template +class ringbuffer_sink final : public base_sink { +public: + explicit ringbuffer_sink(size_t n_items) + : q_{n_items} { + if (n_items == 0) { + throw_spdlog_ex("ringbuffer_sink: n_items cannot be zero"); + } + } + + std::vector last_raw(size_t lim = 0) { + std::lock_guard lock(base_sink::mutex_); + auto items_available = q_.size(); + auto n_items = lim > 0 ? (std::min)(lim, items_available) : items_available; + std::vector ret; + ret.reserve(n_items); + for (size_t i = (items_available - n_items); i < items_available; i++) { + ret.push_back(q_.at(i)); + } + return ret; + } + + std::vector last_formatted(size_t lim = 0) { + std::lock_guard lock(base_sink::mutex_); + auto items_available = q_.size(); + auto n_items = lim > 0 ? (std::min)(lim, items_available) : items_available; + std::vector ret; + ret.reserve(n_items); + for (size_t i = (items_available - n_items); i < items_available; i++) { + memory_buf_t formatted; + base_sink::formatter_->format(q_.at(i), formatted); + ret.push_back(SPDLOG_BUF_TO_STRING(formatted)); + } + return ret; + } + +protected: + void sink_it_(const details::log_msg &msg) override { + q_.push_back(details::log_msg_buffer{msg}); + } + void flush_() override {} + +private: + details::circular_q q_; +}; + +using ringbuffer_sink_mt = ringbuffer_sink; +using ringbuffer_sink_st = ringbuffer_sink; + +} // namespace sinks + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/rotating_file_sink-inl.h b/lib/spdlog/include/spdlog/sinks/rotating_file_sink-inl.h new file mode 100644 index 0000000..370fa7c --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/rotating_file_sink-inl.h @@ -0,0 +1,179 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +template +SPDLOG_INLINE rotating_file_sink::rotating_file_sink( + filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open, + const file_event_handlers &event_handlers) + : base_filename_(std::move(base_filename)), + max_size_(max_size), + max_files_(max_files), + file_helper_{event_handlers} { + if (max_size == 0) { + throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); + } + + if (max_files > MaxFiles) { + throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed MaxFiles"); + } + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + if (rotate_on_open && current_size_ > 0) { + rotate_(); + current_size_ = 0; + } +} + +// calc filename according to index and file extension if exists. +// e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". +template +SPDLOG_INLINE filename_t rotating_file_sink::calc_filename(const filename_t &filename, + std::size_t index) { + if (index == 0U) { + return filename; + } + + filename_t basename; + filename_t ext; + std::tie(basename, ext) = details::file_helper::split_by_extension(filename); + return fmt_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}.{}{}")), basename, index, ext); +} + +template +SPDLOG_INLINE filename_t rotating_file_sink::filename() { + std::lock_guard lock(base_sink::mutex_); + return file_helper_.filename(); +} + +template +SPDLOG_INLINE void rotating_file_sink::rotate_now() { + std::lock_guard lock(base_sink::mutex_); + rotate_(); +} +template +SPDLOG_INLINE void rotating_file_sink::set_max_size(std::size_t max_size) { + std::lock_guard lock(base_sink::mutex_); + if (max_size == 0) { + throw_spdlog_ex("rotating sink set_max_size: max_size arg cannot be zero"); + } + max_size_ = max_size; +} + +template +SPDLOG_INLINE std::size_t rotating_file_sink::get_max_size() { + std::lock_guard lock(base_sink::mutex_); + return max_size_; +} + +template +SPDLOG_INLINE void rotating_file_sink::set_max_files(std::size_t max_files) { + std::lock_guard lock(base_sink::mutex_); + if (max_files > MaxFiles) { + throw_spdlog_ex("rotating sink set_max_files: max_files arg cannot exceed 200000"); + } + max_files_ = max_files; +} + +template +std::size_t rotating_file_sink::get_max_files() { + std::lock_guard lock(base_sink::mutex_); + return max_files_; +} + +template +SPDLOG_INLINE void rotating_file_sink::sink_it_(const details::log_msg &msg) { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + auto new_size = current_size_ + formatted.size(); + + // rotate if the new estimated file size exceeds max size. + // rotate only if the real size > 0 to better deal with full disk (see issue #2261). + // we only check the real size when new_size > max_size_ because it is relatively expensive. + if (new_size > max_size_) { + file_helper_.flush(); + if (file_helper_.size() > 0) { + rotate_(); + new_size = formatted.size(); + } + } + file_helper_.write(formatted); + current_size_ = new_size; +} + +template +SPDLOG_INLINE void rotating_file_sink::flush_() { + file_helper_.flush(); +} + +// Rotate files: +// log.txt -> log.1.txt +// log.1.txt -> log.2.txt +// log.2.txt -> log.3.txt +// log.3.txt -> delete +template +SPDLOG_INLINE void rotating_file_sink::rotate_() { + using details::os::filename_to_str; + using details::os::path_exists; + + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) { + filename_t src = calc_filename(base_filename_, i - 1); + if (!path_exists(src)) { + continue; + } + filename_t target = calc_filename(base_filename_, i); + + if (!rename_file_(src, target)) { + // if failed try again after a small delay. + // this is a workaround to a windows issue, where very high rotation + // rates can cause the rename to fail with permission denied (because of antivirus?). + details::os::sleep_for_millis(100); + if (!rename_file_(src, target)) { + file_helper_.reopen( + true); // truncate the log file anyway to prevent it to grow beyond its limit! + current_size_ = 0; + throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + + " to " + filename_to_str(target), + errno); + } + } + } + file_helper_.reopen(true); +} + +// delete the target if exists, and rename the src file to target +// return true on success, false otherwise. +template +SPDLOG_INLINE bool rotating_file_sink::rename_file_(const filename_t &src_filename, + const filename_t &target_filename) { + // try to delete the target file in case it already exists. + (void)details::os::remove(target_filename); + return details::os::rename(src_filename, target_filename) == 0; +} + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/rotating_file_sink.h b/lib/spdlog/include/spdlog/sinks/rotating_file_sink.h new file mode 100644 index 0000000..522dab6 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/rotating_file_sink.h @@ -0,0 +1,93 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size +// +template +class rotating_file_sink final : public base_sink { +public: + static constexpr size_t MaxFiles = 200000; + rotating_file_sink(filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}); + static filename_t calc_filename(const filename_t &filename, std::size_t index); + filename_t filename(); + void rotate_now(); + void set_max_size(std::size_t max_size); + std::size_t get_max_size(); + void set_max_files(std::size_t max_files); + std::size_t get_max_files(); + +protected: + void sink_it_(const details::log_msg &msg) override; + void flush_() override; + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_(); + + // delete the target if exists, and rename the src file to target + // return true on success, false otherwise. + bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); + + filename_t base_filename_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; +}; + +using rotating_file_sink_mt = rotating_file_sink; +using rotating_file_sink_st = rotating_file_sink; + +} // namespace sinks + +// +// factory functions +// +template +std::shared_ptr rotating_logger_mt(const std::string &logger_name, + const filename_t &filename, + size_t max_file_size, + size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); +} + +template +std::shared_ptr rotating_logger_st(const std::string &logger_name, + const filename_t &filename, + size_t max_file_size, + size_t max_files, + bool rotate_on_open = false, + const file_event_handlers &event_handlers = {}) { + return Factory::template create( + logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); +} +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "rotating_file_sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/sink-inl.h b/lib/spdlog/include/spdlog/sinks/sink-inl.h new file mode 100644 index 0000000..7ffb33e --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/sink-inl.h @@ -0,0 +1,22 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include + +SPDLOG_INLINE bool spdlog::sinks::sink::should_log(spdlog::level::level_enum msg_level) const { + return msg_level >= level_.load(std::memory_order_relaxed); +} + +SPDLOG_INLINE void spdlog::sinks::sink::set_level(level::level_enum log_level) { + level_.store(log_level, std::memory_order_relaxed); +} + +SPDLOG_INLINE spdlog::level::level_enum spdlog::sinks::sink::level() const { + return static_cast(level_.load(std::memory_order_relaxed)); +} diff --git a/lib/spdlog/include/spdlog/sinks/sink.h b/lib/spdlog/include/spdlog/sinks/sink.h new file mode 100644 index 0000000..9c39522 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/sink.h @@ -0,0 +1,34 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +namespace spdlog { + +namespace sinks { +class SPDLOG_API sink { +public: + virtual ~sink() = default; + virtual void log(const details::log_msg &msg) = 0; + virtual void flush() = 0; + virtual void set_pattern(const std::string &pattern) = 0; + virtual void set_formatter(std::unique_ptr sink_formatter) = 0; + + void set_level(level::level_enum log_level); + level::level_enum level() const; + bool should_log(level::level_enum msg_level) const; + +protected: + // sink log level - default is all + level_t level_{level::trace}; +}; + +} // namespace sinks +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/stdout_color_sinks-inl.h b/lib/spdlog/include/spdlog/sinks/stdout_color_sinks-inl.h new file mode 100644 index 0000000..74c8113 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/stdout_color_sinks-inl.h @@ -0,0 +1,38 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { + +template +SPDLOG_INLINE std::shared_ptr stdout_color_mt(const std::string &logger_name, + color_mode mode) { + return Factory::template create(logger_name, mode); +} + +template +SPDLOG_INLINE std::shared_ptr stdout_color_st(const std::string &logger_name, + color_mode mode) { + return Factory::template create(logger_name, mode); +} + +template +SPDLOG_INLINE std::shared_ptr stderr_color_mt(const std::string &logger_name, + color_mode mode) { + return Factory::template create(logger_name, mode); +} + +template +SPDLOG_INLINE std::shared_ptr stderr_color_st(const std::string &logger_name, + color_mode mode) { + return Factory::template create(logger_name, mode); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/stdout_color_sinks.h b/lib/spdlog/include/spdlog/sinks/stdout_color_sinks.h new file mode 100644 index 0000000..9ce2672 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/stdout_color_sinks.h @@ -0,0 +1,49 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifdef _WIN32 +#include +#else +#include +#endif + +#include + +namespace spdlog { +namespace sinks { +#ifdef _WIN32 +using stdout_color_sink_mt = wincolor_stdout_sink_mt; +using stdout_color_sink_st = wincolor_stdout_sink_st; +using stderr_color_sink_mt = wincolor_stderr_sink_mt; +using stderr_color_sink_st = wincolor_stderr_sink_st; +#else +using stdout_color_sink_mt = ansicolor_stdout_sink_mt; +using stdout_color_sink_st = ansicolor_stdout_sink_st; +using stderr_color_sink_mt = ansicolor_stderr_sink_mt; +using stderr_color_sink_st = ansicolor_stderr_sink_st; +#endif +} // namespace sinks + +template +std::shared_ptr stdout_color_mt(const std::string &logger_name, + color_mode mode = color_mode::automatic); + +template +std::shared_ptr stdout_color_st(const std::string &logger_name, + color_mode mode = color_mode::automatic); + +template +std::shared_ptr stderr_color_mt(const std::string &logger_name, + color_mode mode = color_mode::automatic); + +template +std::shared_ptr stderr_color_st(const std::string &logger_name, + color_mode mode = color_mode::automatic); + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "stdout_color_sinks-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/stdout_sinks-inl.h b/lib/spdlog/include/spdlog/sinks/stdout_sinks-inl.h new file mode 100644 index 0000000..74ec141 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/stdout_sinks-inl.h @@ -0,0 +1,127 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include +#include +#include + +#ifdef _WIN32 +// under windows using fwrite to non-binary stream results in \r\r\n (see issue #1675) +// so instead we use ::FileWrite +#include + +#ifndef _USING_V110_SDK71_ // fileapi.h doesn't exist in winxp +#include // WriteFile (..) +#endif + +#include // _get_osfhandle(..) +#include // _fileno(..) +#endif // _WIN32 + +namespace spdlog { + +namespace sinks { + +template +SPDLOG_INLINE stdout_sink_base::stdout_sink_base(FILE *file) + : mutex_(ConsoleMutex::mutex()), + file_(file), + formatter_(details::make_unique()) { +#ifdef _WIN32 + // get windows handle from the FILE* object + + handle_ = reinterpret_cast(::_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 +SPDLOG_INLINE void stdout_sink_base::log(const details::log_msg &msg) { +#ifdef _WIN32 + if (handle_ == INVALID_HANDLE_VALUE) { + return; + } + std::lock_guard lock(mutex_); + memory_buf_t formatted; + formatter_->format(msg, formatted); + auto size = static_cast(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 lock(mutex_); + memory_buf_t formatted; + formatter_->format(msg, formatted); + details::os::fwrite_bytes(formatted.data(), formatted.size(), file_); +#endif // _WIN32 + ::fflush(file_); // flush every line to terminal +} + +template +SPDLOG_INLINE void stdout_sink_base::flush() { + std::lock_guard lock(mutex_); + fflush(file_); +} + +template +SPDLOG_INLINE void stdout_sink_base::set_pattern(const std::string &pattern) { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); +} + +template +SPDLOG_INLINE void stdout_sink_base::set_formatter( + std::unique_ptr sink_formatter) { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); +} + +// stdout sink +template +SPDLOG_INLINE stdout_sink::stdout_sink() + : stdout_sink_base(stdout) {} + +// stderr sink +template +SPDLOG_INLINE stderr_sink::stderr_sink() + : stdout_sink_base(stderr) {} + +} // namespace sinks + +// factory methods +template +SPDLOG_INLINE std::shared_ptr stdout_logger_mt(const std::string &logger_name) { + return Factory::template create(logger_name); +} + +template +SPDLOG_INLINE std::shared_ptr stdout_logger_st(const std::string &logger_name) { + return Factory::template create(logger_name); +} + +template +SPDLOG_INLINE std::shared_ptr stderr_logger_mt(const std::string &logger_name) { + return Factory::template create(logger_name); +} + +template +SPDLOG_INLINE std::shared_ptr stderr_logger_st(const std::string &logger_name) { + return Factory::template create(logger_name); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/stdout_sinks.h b/lib/spdlog/include/spdlog/sinks/stdout_sinks.h new file mode 100644 index 0000000..0b36c16 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,84 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +namespace spdlog { + +namespace sinks { + +template +class stdout_sink_base : public sink { +public: + using mutex_t = typename ConsoleMutex::mutex_t; + explicit stdout_sink_base(FILE *file); + ~stdout_sink_base() override = default; + + stdout_sink_base(const stdout_sink_base &other) = delete; + stdout_sink_base(stdout_sink_base &&other) = delete; + + stdout_sink_base &operator=(const stdout_sink_base &other) = delete; + stdout_sink_base &operator=(stdout_sink_base &&other) = delete; + + void log(const details::log_msg &msg) override; + void flush() override; + void set_pattern(const std::string &pattern) override; + + void set_formatter(std::unique_ptr sink_formatter) override; + +protected: + mutex_t &mutex_; + FILE *file_; + std::unique_ptr formatter_; +#ifdef _WIN32 + HANDLE handle_; +#endif // WIN32 +}; + +template +class stdout_sink : public stdout_sink_base { +public: + stdout_sink(); +}; + +template +class stderr_sink : public stdout_sink_base { +public: + stderr_sink(); +}; + +using stdout_sink_mt = stdout_sink; +using stdout_sink_st = stdout_sink; + +using stderr_sink_mt = stderr_sink; +using stderr_sink_st = stderr_sink; + +} // namespace sinks + +// factory methods +template +std::shared_ptr stdout_logger_mt(const std::string &logger_name); + +template +std::shared_ptr stdout_logger_st(const std::string &logger_name); + +template +std::shared_ptr stderr_logger_mt(const std::string &logger_name); + +template +std::shared_ptr stderr_logger_st(const std::string &logger_name); + +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "stdout_sinks-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/sinks/syslog_sink.h b/lib/spdlog/include/spdlog/sinks/syslog_sink.h new file mode 100644 index 0000000..913d41b --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/syslog_sink.h @@ -0,0 +1,104 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace spdlog { +namespace sinks { +/** + * Sink that write to syslog using the `syscall()` library call. + */ +template +class syslog_sink : public base_sink { +public: + syslog_sink(std::string ident, int syslog_option, int syslog_facility, bool enable_formatting) + : enable_formatting_{enable_formatting}, + syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, + /* spdlog::level::debug */ LOG_DEBUG, + /* spdlog::level::info */ LOG_INFO, + /* spdlog::level::warn */ LOG_WARNING, + /* spdlog::level::err */ LOG_ERR, + /* spdlog::level::critical */ LOG_CRIT, + /* spdlog::level::off */ LOG_INFO}}, + ident_{std::move(ident)} { + // set ident to be program name if empty + ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); + } + + ~syslog_sink() override { ::closelog(); } + + syslog_sink(const syslog_sink &) = delete; + syslog_sink &operator=(const syslog_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override { + string_view_t payload; + memory_buf_t formatted; + if (enable_formatting_) { + base_sink::formatter_->format(msg, formatted); + payload = string_view_t(formatted.data(), formatted.size()); + } else { + payload = msg.payload; + } + + size_t length = payload.size(); + // limit to max int + if (length > static_cast(std::numeric_limits::max())) { + length = static_cast(std::numeric_limits::max()); + } + + ::syslog(syslog_prio_from_level(msg), "%.*s", static_cast(length), payload.data()); + } + + void flush_() override {} + bool enable_formatting_ = false; + + // + // Simply maps spdlog's log level to syslog priority level. + // + virtual int syslog_prio_from_level(const details::log_msg &msg) const { + return syslog_levels_.at(static_cast(msg.level)); + } + + using levels_array = std::array; + levels_array syslog_levels_; + +private: + // must store the ident because the man says openlog might use the pointer as + // is and not a string copy + const std::string ident_; +}; + +using syslog_sink_mt = syslog_sink; +using syslog_sink_st = syslog_sink; +} // namespace sinks + +// Create and register a syslog logger +template +inline std::shared_ptr syslog_logger_mt(const std::string &logger_name, + const std::string &syslog_ident = "", + int syslog_option = 0, + int syslog_facility = LOG_USER, + bool enable_formatting = false) { + return Factory::template create(logger_name, syslog_ident, syslog_option, + syslog_facility, enable_formatting); +} + +template +inline std::shared_ptr syslog_logger_st(const std::string &logger_name, + const std::string &syslog_ident = "", + int syslog_option = 0, + int syslog_facility = LOG_USER, + bool enable_formatting = false) { + return Factory::template create(logger_name, syslog_ident, syslog_option, + syslog_facility, enable_formatting); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/systemd_sink.h b/lib/spdlog/include/spdlog/sinks/systemd_sink.h new file mode 100644 index 0000000..1ad32ce --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/systemd_sink.h @@ -0,0 +1,121 @@ +// Copyright(c) 2019 ZVYAGIN.Alexander@gmail.com +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#include +#ifndef SD_JOURNAL_SUPPRESS_LOCATION +#define SD_JOURNAL_SUPPRESS_LOCATION +#endif +#include + +namespace spdlog { +namespace sinks { + +/** + * Sink that write to systemd journal using the `sd_journal_send()` library call. + */ +template +class systemd_sink : public base_sink { +public: + systemd_sink(std::string ident = "", bool enable_formatting = false) + : ident_{std::move(ident)}, + enable_formatting_{enable_formatting}, + syslog_levels_{{/* spdlog::level::trace */ LOG_DEBUG, + /* spdlog::level::debug */ LOG_DEBUG, + /* spdlog::level::info */ LOG_INFO, + /* spdlog::level::warn */ LOG_WARNING, + /* spdlog::level::err */ LOG_ERR, + /* spdlog::level::critical */ LOG_CRIT, + /* spdlog::level::off */ LOG_INFO}} {} + + ~systemd_sink() override {} + + systemd_sink(const systemd_sink &) = delete; + systemd_sink &operator=(const systemd_sink &) = delete; + +protected: + const std::string ident_; + bool enable_formatting_ = false; + using levels_array = std::array; + levels_array syslog_levels_; + + void sink_it_(const details::log_msg &msg) override { + int err; + string_view_t payload; + memory_buf_t formatted; + if (enable_formatting_) { + base_sink::formatter_->format(msg, formatted); + payload = string_view_t(formatted.data(), formatted.size()); + } else { + payload = msg.payload; + } + + size_t length = payload.size(); + // limit to max int + if (length > static_cast(std::numeric_limits::max())) { + length = static_cast(std::numeric_limits::max()); + } + + const string_view_t syslog_identifier = ident_.empty() ? msg.logger_name : ident_; + + // Do not send source location if not available + if (msg.source.empty()) { + // Note: function call inside '()' to avoid macro expansion + err = (sd_journal_send)("MESSAGE=%.*s", static_cast(length), payload.data(), + "PRIORITY=%d", syslog_level(msg.level), +#ifndef SPDLOG_NO_THREAD_ID + "TID=%zu", msg.thread_id, +#endif + "SYSLOG_IDENTIFIER=%.*s", + static_cast(syslog_identifier.size()), + syslog_identifier.data(), nullptr); + } else { + err = (sd_journal_send)("MESSAGE=%.*s", static_cast(length), payload.data(), + "PRIORITY=%d", syslog_level(msg.level), +#ifndef SPDLOG_NO_THREAD_ID + "TID=%zu", msg.thread_id, +#endif + "SYSLOG_IDENTIFIER=%.*s", + static_cast(syslog_identifier.size()), + syslog_identifier.data(), "CODE_FILE=%s", msg.source.filename, + "CODE_LINE=%d", msg.source.line, "CODE_FUNC=%s", + msg.source.funcname, nullptr); + } + + if (err) { + throw_spdlog_ex("Failed writing to systemd", errno); + } + } + + int syslog_level(level::level_enum l) { + return syslog_levels_.at(static_cast(l)); + } + + void flush_() override {} +}; + +using systemd_sink_mt = systemd_sink; +using systemd_sink_st = systemd_sink; +} // namespace sinks + +// Create and register a syslog logger +template +inline std::shared_ptr systemd_logger_mt(const std::string &logger_name, + const std::string &ident = "", + bool enable_formatting = false) { + return Factory::template create(logger_name, ident, enable_formatting); +} + +template +inline std::shared_ptr systemd_logger_st(const std::string &logger_name, + const std::string &ident = "", + bool enable_formatting = false) { + return Factory::template create(logger_name, ident, enable_formatting); +} +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/tcp_sink.h b/lib/spdlog/include/spdlog/sinks/tcp_sink.h new file mode 100644 index 0000000..cffd433 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/tcp_sink.h @@ -0,0 +1,89 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include +#include +#include + +#pragma once + +// Simple tcp client sink +// Connects to remote address and send the formatted log. +// Will attempt to reconnect if connection drops. +// If more complicated behaviour is needed (i.e get responses), you can inherit it and override the +// sink_it_ method. + +namespace spdlog { +namespace sinks { + +struct tcp_sink_config { + std::string server_host; + int server_port; + int timeout_ms = + 0; // The timeout for all 3 major socket operations that is connect, send, and recv + bool lazy_connect = false; // if true connect on first log call instead of on construction + + tcp_sink_config(std::string host, int port) + : server_host{std::move(host)}, + server_port{port} {} +}; + +template +class tcp_sink : public spdlog::sinks::base_sink { +public: + // connect to tcp host/port or throw if failed + // host can be hostname or ip address + + explicit tcp_sink(const std::string &host, + int port, + int timeout_ms = 0, + bool lazy_connect = false) + : config_{host, port} { + config_.timeout_ms = timeout_ms; + config_.lazy_connect = lazy_connect; + if (!config_.lazy_connect) { + client_.connect(config_.server_host, config_.server_port, config_.timeout_ms); + } + } + + explicit tcp_sink(tcp_sink_config sink_config) + : config_{std::move(sink_config)} { + if (!config_.lazy_connect) { + client_.connect(config_.server_host, config_.server_port, config_.timeout_ms); + } + } + + ~tcp_sink() override = default; + +protected: + void sink_it_(const spdlog::details::log_msg &msg) override { + spdlog::memory_buf_t formatted; + spdlog::sinks::base_sink::formatter_->format(msg, formatted); + if (!client_.is_connected()) { + client_.connect(config_.server_host, config_.server_port, config_.timeout_ms); + } + client_.send(formatted.data(), formatted.size()); + } + + void flush_() override {} + tcp_sink_config config_; + details::tcp_client client_; +}; + +using tcp_sink_mt = tcp_sink; +using tcp_sink_st = tcp_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/udp_sink.h b/lib/spdlog/include/spdlog/sinks/udp_sink.h new file mode 100644 index 0000000..9980ef7 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/udp_sink.h @@ -0,0 +1,69 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include +#include +#include +#include + +// Simple udp client sink +// Sends formatted log via udp + +namespace spdlog { +namespace sinks { + +struct udp_sink_config { + std::string server_host; + uint16_t server_port; + + udp_sink_config(std::string host, uint16_t port) + : server_host{std::move(host)}, + server_port{port} {} +}; + +template +class udp_sink : public spdlog::sinks::base_sink { +public: + // host can be hostname or ip address + explicit udp_sink(const udp_sink_config& sink_config) + : client_{sink_config.server_host, sink_config.server_port} {} + + ~udp_sink() override = default; + +protected: + void sink_it_(const spdlog::details::log_msg &msg) override { + spdlog::memory_buf_t formatted; + spdlog::sinks::base_sink::formatter_->format(msg, formatted); + client_.send(formatted.data(), formatted.size()); + } + + void flush_() override {} + details::udp_client client_; +}; + +using udp_sink_mt = udp_sink; +using udp_sink_st = udp_sink; + +} // namespace sinks + +// +// factory functions +// +template +inline std::shared_ptr udp_logger_mt(const std::string &logger_name, + sinks::udp_sink_config skin_config) { + return Factory::template create(logger_name, skin_config); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/win_eventlog_sink.h b/lib/spdlog/include/spdlog/sinks/win_eventlog_sink.h new file mode 100644 index 0000000..2c9b582 --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/win_eventlog_sink.h @@ -0,0 +1,260 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +// Writing to Windows Event Log requires the registry entries below to be present, with the +// following modifications: +// 1. should be replaced with your log name (e.g. your application name) +// 2. should be replaced with the specific source name and the key should be +// duplicated for +// each source used in the application +// +// Since typically modifications of this kind require elevation, it's better to do it as a part of +// setup procedure. The snippet below uses mscoree.dll as the message file as it exists on most of +// the Windows systems anyway and happens to contain the needed resource. +// +// You can also specify a custom message file if needed. +// Please refer to Event Log functions descriptions in MSDN for more details on custom message +// files. + +/*--------------------------------------------------------------------------------------- + +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\] + +[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\\] +"TypesSupported"=dword:00000007 +"EventMessageFile"=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,\ + 00,6f,00,74,00,25,00,5c,00,53,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,\ + 5c,00,6d,00,73,00,63,00,6f,00,72,00,65,00,65,00,2e,00,64,00,6c,00,6c,00,00,\ + 00 + +-----------------------------------------------------------------------------------------*/ + +#pragma once + +#include +#include + +#include +#include + +#include +#include +#include + +namespace spdlog { +namespace sinks { + +namespace win_eventlog { + +namespace internal { + +struct local_alloc_t { + HLOCAL hlocal_; + + SPDLOG_CONSTEXPR local_alloc_t() SPDLOG_NOEXCEPT : hlocal_(nullptr) {} + + local_alloc_t(local_alloc_t const &) = delete; + local_alloc_t &operator=(local_alloc_t const &) = delete; + + ~local_alloc_t() SPDLOG_NOEXCEPT { + if (hlocal_) { + LocalFree(hlocal_); + } + } +}; + +/** Windows error */ +struct win32_error : public spdlog_ex { + /** Formats an error report line: "user-message: error-code (system message)" */ + static std::string format(std::string const &user_message, DWORD error_code = GetLastError()) { + std::string system_message; + + local_alloc_t format_message_result{}; + auto format_message_succeeded = + ::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.hlocal_, 0, nullptr); + + if (format_message_succeeded && format_message_result.hlocal_) { + system_message = fmt_lib::format(" ({})", (LPSTR)format_message_result.hlocal_); + } + + return fmt_lib::format("{}: {}{}", user_message, error_code, system_message); + } + + explicit win32_error(std::string const &func_name, DWORD error = GetLastError()) + : spdlog_ex(format(func_name, error)) {} +}; + +/** Wrapper for security identifiers (SID) on Windows */ +struct sid_t { + std::vector buffer_; + +public: + sid_t() {} + + /** creates a wrapped SID copy */ + static sid_t duplicate_sid(PSID psid) { + if (!::IsValidSid(psid)) { + throw_spdlog_ex("sid_t::sid_t(): invalid SID received"); + } + + auto const sid_length{::GetLengthSid(psid)}; + + sid_t result; + result.buffer_.resize(sid_length); + if (!::CopySid(sid_length, (PSID)result.as_sid(), psid)) { + SPDLOG_THROW(win32_error("CopySid")); + } + + return result; + } + + /** Retrieves pointer to the internal buffer contents as SID* */ + SID *as_sid() const { return buffer_.empty() ? nullptr : (SID *)buffer_.data(); } + + /** Get SID for the current user */ + static sid_t get_current_user_sid() { + /* create and init RAII holder for process token */ + struct process_token_t { + HANDLE token_handle_ = INVALID_HANDLE_VALUE; + explicit process_token_t(HANDLE process) { + if (!::OpenProcessToken(process, TOKEN_QUERY, &token_handle_)) { + SPDLOG_THROW(win32_error("OpenProcessToken")); + } + } + + ~process_token_t() { ::CloseHandle(token_handle_); } + + } current_process_token( + ::GetCurrentProcess()); // GetCurrentProcess returns pseudohandle, no leak here! + + // Get the required size, this is expected to fail with ERROR_INSUFFICIENT_BUFFER and return + // the token size + DWORD tusize = 0; + if (::GetTokenInformation(current_process_token.token_handle_, TokenUser, NULL, 0, + &tusize)) { + SPDLOG_THROW(win32_error("GetTokenInformation should fail")); + } + + // get user token + std::vector buffer(static_cast(tusize)); + if (!::GetTokenInformation(current_process_token.token_handle_, TokenUser, + (LPVOID)buffer.data(), tusize, &tusize)) { + SPDLOG_THROW(win32_error("GetTokenInformation")); + } + + // create a wrapper of the SID data as stored in the user token + return sid_t::duplicate_sid(((TOKEN_USER *)buffer.data())->User.Sid); + } +}; + +struct eventlog { + static WORD get_event_type(details::log_msg const &msg) { + switch (msg.level) { + case level::trace: + case level::debug: + return EVENTLOG_SUCCESS; + + case level::info: + return EVENTLOG_INFORMATION_TYPE; + + case level::warn: + return EVENTLOG_WARNING_TYPE; + + case level::err: + case level::critical: + case level::off: + return EVENTLOG_ERROR_TYPE; + + default: + return EVENTLOG_INFORMATION_TYPE; + } + } + + static WORD get_event_category(details::log_msg const &msg) { return (WORD)msg.level; } +}; + +} // namespace internal + +/* + * Windows Event Log sink + */ +template +class win_eventlog_sink : public base_sink { +private: + HANDLE hEventLog_{NULL}; + internal::sid_t current_user_sid_; + std::string source_; + DWORD event_id_; + + HANDLE event_log_handle() { + if (!hEventLog_) { + hEventLog_ = ::RegisterEventSourceA(nullptr, source_.c_str()); + if (!hEventLog_ || hEventLog_ == (HANDLE)ERROR_ACCESS_DENIED) { + SPDLOG_THROW(internal::win32_error("RegisterEventSource")); + } + } + + return hEventLog_; + } + +protected: + void sink_it_(const details::log_msg &msg) override { + using namespace internal; + + bool succeeded; + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + formatted.push_back('\0'); + +#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 = static_cast(::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 = static_cast(::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)); +#endif + + if (!succeeded) { + SPDLOG_THROW(win32_error("ReportEvent")); + } + } + + void flush_() override {} + +public: + win_eventlog_sink(std::string const &source, + DWORD event_id = 1000 /* according to mscoree.dll */) + : source_(source), + event_id_(event_id) { + try { + current_user_sid_ = internal::sid_t::get_current_user_sid(); + } catch (...) { + // get_current_user_sid() is unlikely to fail and if it does, we can still proceed + // without current_user_sid but in the event log the record will have no user name + } + } + + ~win_eventlog_sink() { + if (hEventLog_) DeregisterEventSource(hEventLog_); + } +}; + +} // namespace win_eventlog + +using win_eventlog_sink_mt = win_eventlog::win_eventlog_sink; +using win_eventlog_sink_st = win_eventlog::win_eventlog_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/wincolor_sink-inl.h b/lib/spdlog/include/spdlog/sinks/wincolor_sink-inl.h new file mode 100644 index 0000000..910115c --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/wincolor_sink-inl.h @@ -0,0 +1,172 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +#include +#include + +namespace spdlog { +namespace sinks { +template +SPDLOG_INLINE wincolor_sink::wincolor_sink(void *out_handle, color_mode mode) + : out_handle_(out_handle), + mutex_(ConsoleMutex::mutex()), + formatter_(details::make_unique()) { + set_color_mode_impl(mode); + // set level colors + colors_[level::trace] = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; // white + colors_[level::debug] = FOREGROUND_GREEN | FOREGROUND_BLUE; // cyan + colors_[level::info] = FOREGROUND_GREEN; // green + colors_[level::warn] = + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY; // intense yellow + 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; +} + +template +SPDLOG_INLINE wincolor_sink::~wincolor_sink() { + this->flush(); +} + +// change the color for the given level +template +void SPDLOG_INLINE wincolor_sink::set_color(level::level_enum level, + std::uint16_t color) { + std::lock_guard lock(mutex_); + colors_[static_cast(level)] = color; +} + +template +void SPDLOG_INLINE wincolor_sink::log(const details::log_msg &msg) { + if (out_handle_ == nullptr || out_handle_ == INVALID_HANDLE_VALUE) { + return; + } + + std::lock_guard lock(mutex_); + msg.color_range_start = 0; + msg.color_range_end = 0; + memory_buf_t formatted; + formatter_->format(msg, formatted); + if (should_do_colors_ && msg.color_range_end > msg.color_range_start) { + // before color range + print_range_(formatted, 0, msg.color_range_start); + // in color range + auto orig_attribs = + static_cast(set_foreground_color_(colors_[static_cast(msg.level)])); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + // reset to orig colors + ::SetConsoleTextAttribute(static_cast(out_handle_), orig_attribs); + print_range_(formatted, msg.color_range_end, formatted.size()); + } else // print without colors if color range is invalid (or color is disabled) + { + write_to_file_(formatted); + } +} + +template +void SPDLOG_INLINE wincolor_sink::flush() { + // windows console always flushed? +} + +template +void SPDLOG_INLINE wincolor_sink::set_pattern(const std::string &pattern) { + std::lock_guard lock(mutex_); + formatter_ = std::unique_ptr(new pattern_formatter(pattern)); +} + +template +void SPDLOG_INLINE +wincolor_sink::set_formatter(std::unique_ptr sink_formatter) { + std::lock_guard lock(mutex_); + formatter_ = std::move(sink_formatter); +} + +template +void SPDLOG_INLINE wincolor_sink::set_color_mode(color_mode mode) { + std::lock_guard lock(mutex_); + set_color_mode_impl(mode); +} + +template +void SPDLOG_INLINE wincolor_sink::set_color_mode_impl(color_mode mode) { + if (mode == color_mode::automatic) { + // should do colors only if out_handle_ points to actual console. + DWORD console_mode; + bool in_console = ::GetConsoleMode(static_cast(out_handle_), &console_mode) != 0; + should_do_colors_ = in_console; + } else { + should_do_colors_ = mode == color_mode::always ? true : false; + } +} + +// set foreground color and return the orig console attributes (for resetting later) +template +std::uint16_t SPDLOG_INLINE +wincolor_sink::set_foreground_color_(std::uint16_t attribs) { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + if (!::GetConsoleScreenBufferInfo(static_cast(out_handle_), &orig_buffer_info)) { + // just return white if failed getting console info + return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } + + // change only the foreground bits (lowest 4 bits) + auto new_attribs = static_cast(attribs) | (orig_buffer_info.wAttributes & 0xfff0); + auto ignored = + ::SetConsoleTextAttribute(static_cast(out_handle_), static_cast(new_attribs)); + (void)(ignored); + return static_cast(orig_buffer_info.wAttributes); // return orig attribs +} + +// print a range of formatted message to console +template +void SPDLOG_INLINE wincolor_sink::print_range_(const memory_buf_t &formatted, + size_t start, + size_t end) { + if (end > start) { +#if defined(SPDLOG_UTF8_TO_WCHAR_CONSOLE) + wmemory_buf_t wformatted; + details::os::utf8_to_wstrbuf(string_view_t(formatted.data() + start, end - start), + wformatted); + auto size = static_cast(wformatted.size()); + auto ignored = ::WriteConsoleW(static_cast(out_handle_), wformatted.data(), size, + nullptr, nullptr); +#else + auto size = static_cast(end - start); + auto ignored = ::WriteConsoleA(static_cast(out_handle_), formatted.data() + start, + size, nullptr, nullptr); +#endif + (void)(ignored); + } +} + +template +void SPDLOG_INLINE wincolor_sink::write_to_file_(const memory_buf_t &formatted) { + auto size = static_cast(formatted.size()); + DWORD bytes_written = 0; + auto ignored = ::WriteFile(static_cast(out_handle_), formatted.data(), size, + &bytes_written, nullptr); + (void)(ignored); +} + +// wincolor_stdout_sink +template +SPDLOG_INLINE wincolor_stdout_sink::wincolor_stdout_sink(color_mode mode) + : wincolor_sink(::GetStdHandle(STD_OUTPUT_HANDLE), mode) {} + +// wincolor_stderr_sink +template +SPDLOG_INLINE wincolor_stderr_sink::wincolor_stderr_sink(color_mode mode) + : wincolor_sink(::GetStdHandle(STD_ERROR_HANDLE), mode) {} +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/sinks/wincolor_sink.h b/lib/spdlog/include/spdlog/sinks/wincolor_sink.h new file mode 100644 index 0000000..743241c --- /dev/null +++ b/lib/spdlog/include/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,82 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with + * colors + */ +template +class wincolor_sink : public sink { +public: + wincolor_sink(void *out_handle, color_mode mode); + ~wincolor_sink() override; + + wincolor_sink(const wincolor_sink &other) = delete; + wincolor_sink &operator=(const wincolor_sink &other) = delete; + + // change the color for the given level + void set_color(level::level_enum level, std::uint16_t color); + void log(const details::log_msg &msg) override; + void flush() override; + void set_pattern(const std::string &pattern) override; + void set_formatter(std::unique_ptr sink_formatter) override; + void set_color_mode(color_mode mode); + +protected: + using mutex_t = typename ConsoleMutex::mutex_t; + void *out_handle_; + mutex_t &mutex_; + bool should_do_colors_; + std::unique_ptr formatter_; + std::array colors_; + + // set foreground color and return the orig console attributes (for resetting later) + std::uint16_t set_foreground_color_(std::uint16_t attribs); + + // print a range of formatted message to console + 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) + void write_to_file_(const memory_buf_t &formatted); + + void set_color_mode_impl(color_mode mode); +}; + +template +class wincolor_stdout_sink : public wincolor_sink { +public: + explicit wincolor_stdout_sink(color_mode mode = color_mode::automatic); +}; + +template +class wincolor_stderr_sink : public wincolor_sink { +public: + explicit wincolor_stderr_sink(color_mode mode = color_mode::automatic); +}; + +using wincolor_stdout_sink_mt = wincolor_stdout_sink; +using wincolor_stdout_sink_st = wincolor_stdout_sink; + +using wincolor_stderr_sink_mt = wincolor_stderr_sink; +using wincolor_stderr_sink_st = wincolor_stderr_sink; +} // namespace sinks +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY +#include "wincolor_sink-inl.h" +#endif diff --git a/lib/spdlog/include/spdlog/spdlog-inl.h b/lib/spdlog/include/spdlog/spdlog-inl.h new file mode 100644 index 0000000..56f1916 --- /dev/null +++ b/lib/spdlog/include/spdlog/spdlog-inl.h @@ -0,0 +1,96 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY +#include +#endif + +#include +#include + +namespace spdlog { + +SPDLOG_INLINE void initialize_logger(std::shared_ptr logger) { + details::registry::instance().initialize_logger(std::move(logger)); +} + +SPDLOG_INLINE std::shared_ptr get(const std::string &name) { + return details::registry::instance().get(name); +} + +SPDLOG_INLINE void set_formatter(std::unique_ptr formatter) { + details::registry::instance().set_formatter(std::move(formatter)); +} + +SPDLOG_INLINE void set_pattern(std::string pattern, pattern_time_type time_type) { + set_formatter( + std::unique_ptr(new pattern_formatter(std::move(pattern), time_type))); +} + +SPDLOG_INLINE void enable_backtrace(size_t n_messages) { + details::registry::instance().enable_backtrace(n_messages); +} + +SPDLOG_INLINE void disable_backtrace() { details::registry::instance().disable_backtrace(); } + +SPDLOG_INLINE void 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) { + details::registry::instance().set_level(log_level); +} + +SPDLOG_INLINE void flush_on(level::level_enum log_level) { + details::registry::instance().flush_on(log_level); +} + +SPDLOG_INLINE void set_error_handler(void (*handler)(const std::string &msg)) { + details::registry::instance().set_error_handler(handler); +} + +SPDLOG_INLINE void register_logger(std::shared_ptr logger) { + details::registry::instance().register_logger(std::move(logger)); +} + +SPDLOG_INLINE void register_or_replace(std::shared_ptr logger) { + details::registry::instance().register_or_replace(std::move(logger)); +} + +SPDLOG_INLINE void apply_all(const std::function)> &fun) { + details::registry::instance().apply_all(fun); +} + +SPDLOG_INLINE void drop(const std::string &name) { details::registry::instance().drop(name); } + +SPDLOG_INLINE void drop_all() { details::registry::instance().drop_all(); } + +SPDLOG_INLINE void shutdown() { details::registry::instance().shutdown(); } + +SPDLOG_INLINE void set_automatic_registration(bool automatic_registration) { + details::registry::instance().set_automatic_registration(automatic_registration); +} + +SPDLOG_INLINE std::shared_ptr default_logger() { + return details::registry::instance().default_logger(); +} + +SPDLOG_INLINE spdlog::logger *default_logger_raw() { + return details::registry::instance().get_default_raw(); +} + +SPDLOG_INLINE void set_default_logger(std::shared_ptr default_logger) { + details::registry::instance().set_default_logger(std::move(default_logger)); +} + +SPDLOG_INLINE void apply_logger_env_levels(std::shared_ptr logger) { + details::registry::instance().apply_logger_env_levels(std::move(logger)); +} + +} // namespace spdlog diff --git a/lib/spdlog/include/spdlog/spdlog.h b/lib/spdlog/include/spdlog/spdlog.h new file mode 100644 index 0000000..649318b --- /dev/null +++ b/lib/spdlog/include/spdlog/spdlog.h @@ -0,0 +1,354 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +// spdlog main header file. +// see example.cpp for usage example + +#ifndef SPDLOG_H +#define SPDLOG_H + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace spdlog { + +using default_factory = synchronous_factory; + +// Create and register a logger with a templated sink type +// The logger's level, formatter and flush level will be set according to the +// global settings. +// +// Example: +// spdlog::create("logger_name", "dailylog_filename", 11, 59); +template +inline std::shared_ptr create(std::string logger_name, SinkArgs &&...sink_args) { + return default_factory::create(std::move(logger_name), + std::forward(sink_args)...); +} + +// Initialize and register a logger, +// formatter and flush level will be set according the global settings. +// +// Useful for initializing manually created loggers with the global settings. +// +// Example: +// auto mylogger = std::make_shared("mylogger", ...); +// spdlog::initialize_logger(mylogger); +SPDLOG_API void initialize_logger(std::shared_ptr logger); + +// Return an existing logger or nullptr if a logger with such a name doesn't +// exist. +// example: spdlog::get("my_logger")->info("hello {}", "world"); +SPDLOG_API std::shared_ptr get(const std::string &name); + +// Set global formatter. Each sink in each logger will get a clone of this object +SPDLOG_API void set_formatter(std::unique_ptr formatter); + +// Set global format string. +// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); +SPDLOG_API void set_pattern(std::string pattern, + pattern_time_type time_type = pattern_time_type::local); + +// enable global backtrace support +SPDLOG_API void enable_backtrace(size_t n_messages); + +// disable global backtrace support +SPDLOG_API void disable_backtrace(); + +// call dump backtrace on default logger +SPDLOG_API void dump_backtrace(); + +// Get global logging level +SPDLOG_API level::level_enum get_level(); + +// Set the global logging 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 log_level); + +// Set a global flush level +SPDLOG_API void flush_on(level::level_enum log_level); + +// Start/Restart a periodic flusher thread +// Warning: Use only if all your loggers are thread safe! +template +inline void flush_every(std::chrono::duration interval) { + details::registry::instance().flush_every(interval); +} + +// Set global error handler +SPDLOG_API void set_error_handler(void (*handler)(const std::string &msg)); + +// Register the given logger with the given name +// Will throw if a logger with the same name already exists. +SPDLOG_API void register_logger(std::shared_ptr logger); + +// Register the given logger with the given name +// Will replace any existing logger with the same name. +SPDLOG_API void register_or_replace(std::shared_ptr logger); + +// Apply a user-defined function on all registered loggers +// Example: +// spdlog::apply_all([&](std::shared_ptr l) {l->flush();}); +SPDLOG_API void apply_all(const std::function)> &fun); + +// Drop the reference to the given logger +SPDLOG_API void drop(const std::string &name); + +// Drop all references from the registry +SPDLOG_API void drop_all(); + +// stop any running threads started by spdlog and clean registry loggers +SPDLOG_API void shutdown(); + +// Automatic registration of loggers when using spdlog::create() or spdlog::create_async +SPDLOG_API void set_automatic_registration(bool automatic_registration); + +// API for using default logger (stdout_color_mt), +// e.g.: spdlog::info("Message {}", 1); +// +// The default logger object can be accessed using the spdlog::default_logger(): +// For example, to add another sink to it: +// spdlog::default_logger()->sinks().push_back(some_sink); +// +// The default logger can be replaced using spdlog::set_default_logger(new_logger). +// For example, to replace it with a file logger. +// +// IMPORTANT: +// The default API is thread safe (for _mt loggers), but: +// set_default_logger() *should not* be used concurrently with the default API. +// e.g., do not call set_default_logger() from one thread while calling spdlog::info() from another. + +SPDLOG_API std::shared_ptr default_logger(); + +SPDLOG_API spdlog::logger *default_logger_raw(); + +SPDLOG_API void set_default_logger(std::shared_ptr default_logger); + +// Initialize logger level based on environment configs. +// +// Useful for applying SPDLOG_LEVEL to manually created loggers. +// +// Example: +// auto mylogger = std::make_shared("mylogger", ...); +// spdlog::apply_logger_env_levels(mylogger); +SPDLOG_API void apply_logger_env_levels(std::shared_ptr logger); + +template +inline void log(source_loc source, + level::level_enum lvl, + format_string_t fmt, + Args &&...args) { + default_logger_raw()->log(source, lvl, fmt, std::forward(args)...); +} + +template +inline void log(level::level_enum lvl, format_string_t fmt, Args &&...args) { + default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward(args)...); +} + +template +inline void trace(format_string_t fmt, Args &&...args) { + default_logger_raw()->trace(fmt, std::forward(args)...); +} + +template +inline void debug(format_string_t fmt, Args &&...args) { + default_logger_raw()->debug(fmt, std::forward(args)...); +} + +template +inline void info(format_string_t fmt, Args &&...args) { + default_logger_raw()->info(fmt, std::forward(args)...); +} + +template +inline void warn(format_string_t fmt, Args &&...args) { + default_logger_raw()->warn(fmt, std::forward(args)...); +} + +template +inline void error(format_string_t fmt, Args &&...args) { + default_logger_raw()->error(fmt, std::forward(args)...); +} + +template +inline void critical(format_string_t fmt, Args &&...args) { + default_logger_raw()->critical(fmt, std::forward(args)...); +} + +template +inline void log(source_loc source, level::level_enum lvl, const T &msg) { + default_logger_raw()->log(source, lvl, msg); +} + +template +inline void log(level::level_enum lvl, const T &msg) { + default_logger_raw()->log(lvl, msg); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +template +inline void log(source_loc source, + level::level_enum lvl, + wformat_string_t fmt, + Args &&...args) { + default_logger_raw()->log(source, lvl, fmt, std::forward(args)...); +} + +template +inline void log(level::level_enum lvl, wformat_string_t fmt, Args &&...args) { + default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward(args)...); +} + +template +inline void trace(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->trace(fmt, std::forward(args)...); +} + +template +inline void debug(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->debug(fmt, std::forward(args)...); +} + +template +inline void info(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->info(fmt, std::forward(args)...); +} + +template +inline void warn(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->warn(fmt, std::forward(args)...); +} + +template +inline void error(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->error(fmt, std::forward(args)...); +} + +template +inline void critical(wformat_string_t fmt, Args &&...args) { + default_logger_raw()->critical(fmt, std::forward(args)...); +} +#endif + +template +inline void trace(const T &msg) { + default_logger_raw()->trace(msg); +} + +template +inline void debug(const T &msg) { + default_logger_raw()->debug(msg); +} + +template +inline void info(const T &msg) { + default_logger_raw()->info(msg); +} + +template +inline void warn(const T &msg) { + default_logger_raw()->warn(msg); +} + +template +inline void error(const T &msg) { + default_logger_raw()->error(msg); +} + +template +inline void critical(const T &msg) { + default_logger_raw()->critical(msg); +} + +} // namespace spdlog + +// +// enable/disable log calls at compile time according to global level. +// +// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h): +// SPDLOG_LEVEL_TRACE, +// SPDLOG_LEVEL_DEBUG, +// SPDLOG_LEVEL_INFO, +// SPDLOG_LEVEL_WARN, +// SPDLOG_LEVEL_ERROR, +// SPDLOG_LEVEL_CRITICAL, +// SPDLOG_LEVEL_OFF +// + +#ifndef SPDLOG_NO_SOURCE_LOC +#define SPDLOG_LOGGER_CALL(logger, level, ...) \ + (logger)->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__) +#else +#define SPDLOG_LOGGER_CALL(logger, level, ...) \ + (logger)->log(spdlog::source_loc{}, level, __VA_ARGS__) +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE +#define SPDLOG_LOGGER_TRACE(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__) +#define SPDLOG_TRACE(...) SPDLOG_LOGGER_TRACE(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_TRACE(logger, ...) (void)0 +#define SPDLOG_TRACE(...) (void)0 +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG +#define SPDLOG_LOGGER_DEBUG(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__) +#define SPDLOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_DEBUG(logger, ...) (void)0 +#define SPDLOG_DEBUG(...) (void)0 +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO +#define SPDLOG_LOGGER_INFO(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__) +#define SPDLOG_INFO(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_INFO(logger, ...) (void)0 +#define SPDLOG_INFO(...) (void)0 +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN +#define SPDLOG_LOGGER_WARN(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__) +#define SPDLOG_WARN(...) SPDLOG_LOGGER_WARN(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_WARN(logger, ...) (void)0 +#define SPDLOG_WARN(...) (void)0 +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR +#define SPDLOG_LOGGER_ERROR(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__) +#define SPDLOG_ERROR(...) SPDLOG_LOGGER_ERROR(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_ERROR(logger, ...) (void)0 +#define SPDLOG_ERROR(...) (void)0 +#endif + +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL +#define SPDLOG_LOGGER_CRITICAL(logger, ...) \ + SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__) +#define SPDLOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(spdlog::default_logger_raw(), __VA_ARGS__) +#else +#define SPDLOG_LOGGER_CRITICAL(logger, ...) (void)0 +#define SPDLOG_CRITICAL(...) (void)0 +#endif + +#ifdef SPDLOG_HEADER_ONLY +#include "spdlog-inl.h" +#endif + +#endif // SPDLOG_H diff --git a/lib/spdlog/include/spdlog/stopwatch.h b/lib/spdlog/include/spdlog/stopwatch.h new file mode 100644 index 0000000..54ab3d3 --- /dev/null +++ b/lib/spdlog/include/spdlog/stopwatch.h @@ -0,0 +1,66 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include +#include + +// 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 +//.. +// using std::chrono::duration_cast; +// using std::chrono::milliseconds; +// spdlog::info("Elapsed {}", duration_cast(sw.elapsed())); => "Elapsed 5ms" + +namespace spdlog { +class stopwatch { + using clock = std::chrono::steady_clock; + std::chrono::time_point start_tp_; + +public: + stopwatch() + : start_tp_{clock::now()} {} + + std::chrono::duration elapsed() const { + return std::chrono::duration(clock::now() - start_tp_); + } + + std::chrono::milliseconds elapsed_ms() const { + return std::chrono::duration_cast(clock::now() - start_tp_); + } + + void reset() { start_tp_ = clock::now(); } +}; +} // namespace spdlog + +// Support for fmt formatting (e.g. "{:012.9}" or just "{}") +namespace +#ifdef SPDLOG_USE_STD_FORMAT + std +#else + fmt +#endif +{ + +template <> +struct formatter : formatter { + template + auto format(const spdlog::stopwatch &sw, FormatContext &ctx) const -> decltype(ctx.out()) { + return formatter::format(sw.elapsed().count(), ctx); + } +}; +} // namespace std diff --git a/lib/spdlog/include/spdlog/tweakme.h b/lib/spdlog/include/spdlog/tweakme.h new file mode 100644 index 0000000..d609298 --- /dev/null +++ b/lib/spdlog/include/spdlog/tweakme.h @@ -0,0 +1,148 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// +// Edit this file to squeeze more performance, and to customize supported +// features +// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. +// This clock is less accurate - can be off by dozens of millis - depending on +// the kernel HZ. +// Uncomment to use it instead of the regular clock. +// +// #define SPDLOG_CLOCK_COARSE +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if source location logging is not needed. +// This will prevent spdlog from using __FILE__, __LINE__ and SPDLOG_FUNCTION +// +// #define SPDLOG_NO_SOURCE_LOC +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). +// This will prevent spdlog from querying the thread id on each log call. +// +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is +// on, zero will be logged as thread id. +// +// #define SPDLOG_NO_THREAD_ID +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from using thread local storage. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined +// thread ids in the children logs. +// +// #define SPDLOG_NO_TLS +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid spdlog's usage of atomic log levels +// Use only if your code never modifies a logger's log levels concurrently by +// different threads. +// +// #define SPDLOG_NO_ATOMIC_LEVELS +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable usage of wchar_t for file names on Windows. +// +// #define SPDLOG_WCHAR_FILENAMES +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) +// +// #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. +// In this case spdlog will try to include so set your -I flag +// accordingly. +// +// #define SPDLOG_FMT_EXTERNAL +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use C++20 std::format instead of fmt. +// +// #define SPDLOG_USE_STD_FORMAT +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable wchar_t support (convert to utf8) +// +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MY TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY +// CRITICAL", "OFF" } +// +// For C++17 use string_view_literals: +// +// #include +// using namespace std::string_view_literals; +// #define SPDLOG_LEVEL_NAMES { "MY TRACE"sv, "MY DEBUG"sv, "MY INFO"sv, "MY WARNING"sv, "MY +// ERROR"sv, "MY CRITICAL"sv, "OFF"sv } +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize short level names (e.g. "MT") +// These can be longer than one character. +// +// #define SPDLOG_SHORT_LEVEL_NAMES { "T", "D", "I", "W", "E", "C", "O" } +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to disable default logger creation. +// This might save some (very) small initialization time if no default logger is needed. +// +// #define SPDLOG_DISABLE_DEFAULT_LOGGER +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment and set to compile time level with zero cost (default is INFO). +// Macros like SPDLOG_DEBUG(..), SPDLOG_INFO(..) will expand to empty statements if not enabled +// +// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment (and change if desired) macro to use for function names. +// This is compiler dependent. +// __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc. +// Defaults to __FUNCTION__ (should work on all compilers) if not defined. +// +// #ifdef __PRETTY_FUNCTION__ +// # define SPDLOG_FUNCTION __PRETTY_FUNCTION__ +// #else +// # define SPDLOG_FUNCTION __FUNCTION__ +// #endif +/////////////////////////////////////////////////////////////////////////////// diff --git a/lib/spdlog/include/spdlog/version.h b/lib/spdlog/include/spdlog/version.h new file mode 100644 index 0000000..f8e0806 --- /dev/null +++ b/lib/spdlog/include/spdlog/version.h @@ -0,0 +1,11 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#define SPDLOG_VER_MAJOR 1 +#define SPDLOG_VER_MINOR 17 +#define SPDLOG_VER_PATCH 0 + +#define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch) +#define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH) diff --git a/lib/spdlog/logos/jetbrains-variant-4.svg b/lib/spdlog/logos/jetbrains-variant-4.svg new file mode 100644 index 0000000..e02b559 --- /dev/null +++ b/lib/spdlog/logos/jetbrains-variant-4.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/spdlog/scripts/ci_setup_clang.sh b/lib/spdlog/scripts/ci_setup_clang.sh new file mode 100644 index 0000000..140f9f9 --- /dev/null +++ b/lib/spdlog/scripts/ci_setup_clang.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -ex + +VERSION=$1 + +apt-get update +apt-get install -y libc++-${VERSION}-dev libc++abi-${VERSION}-dev + +if [[ "${VERSION}" -ge 12 ]]; then + apt-get install -y --no-install-recommends libunwind-${VERSION}-dev +fi diff --git a/lib/spdlog/scripts/extract_version.py b/lib/spdlog/scripts/extract_version.py new file mode 100644 index 0000000..79b5728 --- /dev/null +++ b/lib/spdlog/scripts/extract_version.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +import os +import re + +base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +config_h = os.path.join(base_path, 'include', 'spdlog', 'version.h') +data = {'MAJOR': 0, 'MINOR': 0, 'PATCH': 0} +reg = re.compile(r'^\s*#define\s+SPDLOG_VER_([A-Z]+)\s+([0-9]+).*$') + +with open(config_h, 'r') as fp: + for l in fp: + m = reg.match(l) + if m: + data[m.group(1)] = int(m.group(2)) + +print(f"{data['MAJOR']}.{data['MINOR']}.{data['PATCH']}") diff --git a/lib/spdlog/scripts/format.sh b/lib/spdlog/scripts/format.sh new file mode 100644 index 0000000..8eb01fb --- /dev/null +++ b/lib/spdlog/scripts/format.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +cd "$(dirname "$0")"/.. +pwd +find_sources="find include src tests example bench -not ( -path include/spdlog/fmt/bundled -prune ) -type f -name *\.h -o -name *\.cpp" +echo -n "Running dos2unix " +$find_sources | xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'" +echo +echo -n "Running clang-format " + +$find_sources | xargs -I {} sh -c "clang-format -i {}; echo -n '.'" + +echo +echo -n "Running cmake-format " +find . -type f -name "CMakeLists.txt" -o -name "*\.cmake"|grep -v bundled|grep -v build|xargs -I {} sh -c "cmake-format --line-width 120 --tab-size 4 --max-subgroups-hwrap 4 -i {}; echo -n '.'" +echo + + + diff --git a/lib/spdlog/src/async.cpp b/lib/spdlog/src/async.cpp new file mode 100644 index 0000000..77a0729 --- /dev/null +++ b/lib/spdlog/src/async.cpp @@ -0,0 +1,11 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include +#include +#include +#include diff --git a/lib/spdlog/src/bundled_fmtlib_format.cpp b/lib/spdlog/src/bundled_fmtlib_format.cpp new file mode 100644 index 0000000..8ec479c --- /dev/null +++ b/lib/spdlog/src/bundled_fmtlib_format.cpp @@ -0,0 +1,48 @@ +// Slightly modified version of fmt lib's format.cc source file. +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#if !defined(SPDLOG_FMT_EXTERNAL) && !defined(SPDLOG_USE_STD_FORMAT) + +#include + +FMT_BEGIN_NAMESPACE + +#if FMT_USE_LOCALE +template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! +template FMT_API auto locale_ref::get() const -> std::locale; +#endif + +namespace detail { + +template FMT_API auto dragonbox::to_decimal(float x) noexcept + -> dragonbox::decimal_fp; +template FMT_API auto dragonbox::to_decimal(double x) noexcept + -> dragonbox::decimal_fp; + +// Explicit instantiations for char. + +template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +template FMT_API auto decimal_point_impl(locale_ref) -> char; + +// DEPRECATED! +template FMT_API void buffer::append(const char*, const char*); + +// Explicit instantiations for wchar_t. + +template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; + +// DEPRECATED! +template FMT_API void buffer::append(const wchar_t*, const wchar_t*); + +} // namespace detail +FMT_END_NAMESPACE + +#endif // !SPDLOG_FMT_EXTERNAL diff --git a/lib/spdlog/src/cfg.cpp b/lib/spdlog/src/cfg.cpp new file mode 100644 index 0000000..98a96ca --- /dev/null +++ b/lib/spdlog/src/cfg.cpp @@ -0,0 +1,8 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include diff --git a/lib/spdlog/src/color_sinks.cpp b/lib/spdlog/src/color_sinks.cpp new file mode 100644 index 0000000..6ee9432 --- /dev/null +++ b/lib/spdlog/src/color_sinks.cpp @@ -0,0 +1,55 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include + +#include +#include +// +// color sinks +// +#ifdef _WIN32 +#include +template class SPDLOG_API spdlog::sinks::wincolor_sink; +template class SPDLOG_API spdlog::sinks::wincolor_sink; +template class SPDLOG_API spdlog::sinks::wincolor_stdout_sink; +template class SPDLOG_API spdlog::sinks::wincolor_stdout_sink; +template class SPDLOG_API spdlog::sinks::wincolor_stderr_sink; +template class SPDLOG_API spdlog::sinks::wincolor_stderr_sink; +#else +#include "spdlog/sinks/ansicolor_sink-inl.h" +template class SPDLOG_API spdlog::sinks::ansicolor_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_stdout_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_stdout_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_stderr_sink; +template class SPDLOG_API spdlog::sinks::ansicolor_stderr_sink; +#endif + +// factory methods for color loggers +#include "spdlog/sinks/stdout_color_sinks-inl.h" +template SPDLOG_API std::shared_ptr +spdlog::stdout_color_mt(const std::string &logger_name, + color_mode mode); +template SPDLOG_API std::shared_ptr +spdlog::stdout_color_st(const std::string &logger_name, + color_mode mode); +template SPDLOG_API std::shared_ptr +spdlog::stderr_color_mt(const std::string &logger_name, + color_mode mode); +template SPDLOG_API std::shared_ptr +spdlog::stderr_color_st(const std::string &logger_name, + color_mode mode); + +template SPDLOG_API std::shared_ptr spdlog::stdout_color_mt( + const std::string &logger_name, color_mode mode); +template SPDLOG_API std::shared_ptr spdlog::stdout_color_st( + const std::string &logger_name, color_mode mode); +template SPDLOG_API std::shared_ptr spdlog::stderr_color_mt( + const std::string &logger_name, color_mode mode); +template SPDLOG_API std::shared_ptr spdlog::stderr_color_st( + const std::string &logger_name, color_mode mode); diff --git a/lib/spdlog/src/file_sinks.cpp b/lib/spdlog/src/file_sinks.cpp new file mode 100644 index 0000000..809883f --- /dev/null +++ b/lib/spdlog/src/file_sinks.cpp @@ -0,0 +1,20 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include +#include +#include +#include + +#include + +template class SPDLOG_API spdlog::sinks::basic_file_sink; +template class SPDLOG_API spdlog::sinks::basic_file_sink; + +#include +template class SPDLOG_API spdlog::sinks::rotating_file_sink; +template class SPDLOG_API spdlog::sinks::rotating_file_sink; diff --git a/lib/spdlog/src/spdlog.cpp b/lib/spdlog/src/spdlog.cpp new file mode 100644 index 0000000..c6c8514 --- /dev/null +++ b/lib/spdlog/src/spdlog.cpp @@ -0,0 +1,28 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// template instantiate logger constructor with sinks init list +template SPDLOG_API spdlog::logger::logger(std::string name, + sinks_init_list::iterator begin, + sinks_init_list::iterator end); +template class SPDLOG_API spdlog::sinks::base_sink; +template class SPDLOG_API spdlog::sinks::base_sink; diff --git a/lib/spdlog/src/stdout_sinks.cpp b/lib/spdlog/src/stdout_sinks.cpp new file mode 100644 index 0000000..d9d8ba4 --- /dev/null +++ b/lib/spdlog/src/stdout_sinks.cpp @@ -0,0 +1,37 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#ifndef SPDLOG_COMPILED_LIB +#error Please define SPDLOG_COMPILED_LIB to compile this file. +#endif + +#include + +#include +#include +#include + +template class SPDLOG_API spdlog::sinks::stdout_sink_base; +template class SPDLOG_API spdlog::sinks::stdout_sink_base; +template class SPDLOG_API spdlog::sinks::stdout_sink; +template class SPDLOG_API spdlog::sinks::stdout_sink; +template class SPDLOG_API spdlog::sinks::stderr_sink; +template class SPDLOG_API spdlog::sinks::stderr_sink; + +template SPDLOG_API std::shared_ptr +spdlog::stdout_logger_mt(const std::string &logger_name); +template SPDLOG_API std::shared_ptr +spdlog::stdout_logger_st(const std::string &logger_name); +template SPDLOG_API std::shared_ptr +spdlog::stderr_logger_mt(const std::string &logger_name); +template SPDLOG_API std::shared_ptr +spdlog::stderr_logger_st(const std::string &logger_name); + +template SPDLOG_API std::shared_ptr spdlog::stdout_logger_mt( + const std::string &logger_name); +template SPDLOG_API std::shared_ptr spdlog::stdout_logger_st( + const std::string &logger_name); +template SPDLOG_API std::shared_ptr spdlog::stderr_logger_mt( + const std::string &logger_name); +template SPDLOG_API std::shared_ptr spdlog::stderr_logger_st( + const std::string &logger_name); diff --git a/lib/spdlog/tests/CMakeLists.txt b/lib/spdlog/tests/CMakeLists.txt new file mode 100644 index 0000000..d495277 --- /dev/null +++ b/lib/spdlog/tests/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.11) +project(spdlog_utests CXX) + +if(NOT TARGET spdlog) + # Stand-alone build + find_package(spdlog REQUIRED) +endif() + +include(../cmake/utils.cmake) + +find_package(PkgConfig) +if(PkgConfig_FOUND) + pkg_check_modules(systemd libsystemd) +endif() + +find_package(Catch2 3 QUIET) +if(Catch2_FOUND) + message(STATUS "Packaged version of Catch will be used.") +else() + message(STATUS "Bundled version of Catch will be downloaded and used.") + include(FetchContent) + FetchContent_Declare( + Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0 + ) + FetchContent_MakeAvailable(Catch2) +endif() + +set(SPDLOG_UTESTS_SOURCES + test_file_helper.cpp + test_file_logging.cpp + test_daily_logger.cpp + test_misc.cpp + test_eventlog.cpp + test_pattern_formatter.cpp + test_async.cpp + test_registry.cpp + test_macros.cpp + utils.cpp + main.cpp + test_mpmc_q.cpp + test_dup_filter.cpp + test_fmt_helper.cpp + test_stdout_api.cpp + test_backtrace.cpp + test_create_dir.cpp + test_custom_callbacks.cpp + test_cfg.cpp + test_time_point.cpp + test_stopwatch.cpp + test_circular_q.cpp + test_bin_to_hex.cpp + test_ringbuffer.cpp + test_timezone.cpp +) + +if(NOT SPDLOG_NO_EXCEPTIONS) + list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp) +endif() + +if(systemd_FOUND) + list(APPEND SPDLOG_UTESTS_SOURCES test_systemd.cpp) +endif() + +enable_testing() + +function(spdlog_prepare_test test_target spdlog_lib) + add_executable(${test_target} ${SPDLOG_UTESTS_SOURCES}) + spdlog_enable_warnings(${test_target}) + target_link_libraries(${test_target} PRIVATE ${spdlog_lib}) + if(systemd_FOUND) + target_link_libraries(${test_target} PRIVATE ${systemd_LIBRARIES}) + endif() + target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain) + if(SPDLOG_SANITIZE_ADDRESS) + spdlog_enable_addr_sanitizer(${test_target}) + elseif(SPDLOG_SANITIZE_THREAD) + spdlog_enable_thread_sanitizer(${test_target}) + endif() + add_test(NAME ${test_target} COMMAND ${test_target} --order decl) + set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON) +endfunction() + +# The compiled library tests +if(SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_ALL) + spdlog_prepare_test(spdlog-utests spdlog::spdlog) +endif() + +# The header-only library version tests +if(SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL) + spdlog_prepare_test(spdlog-utests-ho spdlog::spdlog_header_only) +endif() diff --git a/lib/spdlog/tests/includes.h b/lib/spdlog/tests/includes.h new file mode 100644 index 0000000..403410f --- /dev/null +++ b/lib/spdlog/tests/includes.h @@ -0,0 +1,45 @@ +#pragma once + +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // Workaround for GCC 12 +#endif +#include +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif + +#include "utils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG + +#undef SPDLOG_LEVEL_NAMES +#undef SPDLOG_SHORT_LEVEL_NAMES + +#include "spdlog/spdlog.h" +#include "spdlog/async.h" +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/os.h" + +#ifndef SPDLOG_NO_TLS +#include "spdlog/mdc.h" +#endif + +#include "spdlog/sinks/basic_file_sink.h" +#include "spdlog/sinks/daily_file_sink.h" +#include "spdlog/sinks/null_sink.h" +#include "spdlog/sinks/ostream_sink.h" +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#include "spdlog/sinks/msvc_sink.h" +#include "spdlog/pattern_formatter.h" diff --git a/lib/spdlog/tests/main.cpp b/lib/spdlog/tests/main.cpp new file mode 100644 index 0000000..e80ceef --- /dev/null +++ b/lib/spdlog/tests/main.cpp @@ -0,0 +1,10 @@ +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // Workaround for GCC 12 +#endif + +#include + +#if defined(__GNUC__) && __GNUC__ == 12 +#pragma GCC diagnostic pop +#endif diff --git a/lib/spdlog/tests/test_async.cpp b/lib/spdlog/tests/test_async.cpp new file mode 100644 index 0000000..76fdd7c --- /dev/null +++ b/lib/spdlog/tests/test_async.cpp @@ -0,0 +1,200 @@ +#include "includes.h" +#include "spdlog/async.h" +#include "spdlog/sinks/basic_file_sink.h" +#include "test_sink.h" + +#define TEST_FILENAME "test_logs/async_test.log" + +TEST_CASE("basic async test ", "[async]") { + auto test_sink = std::make_shared(); + size_t overrun_counter = 0; + size_t queue_size = 128; + size_t messages = 256; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + logger->flush(); + overrun_counter = tp->overrun_counter(); + } + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); + REQUIRE(overrun_counter == 0); +} + +TEST_CASE("discard policy ", "[async]") { + auto test_sink = std::make_shared(); + test_sink->set_delay(std::chrono::milliseconds(1)); + size_t queue_size = 4; + size_t messages = 1024; + + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared( + "as", test_sink, tp, spdlog::async_overflow_policy::overrun_oldest); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + REQUIRE(test_sink->msg_counter() < messages); + REQUIRE(tp->overrun_counter() > 0); +} + +TEST_CASE("discard policy discard_new ", "[async]") { + auto test_sink = std::make_shared(); + test_sink->set_delay(std::chrono::milliseconds(1)); + size_t queue_size = 4; + size_t messages = 1024; + + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared( + "as", test_sink, tp, spdlog::async_overflow_policy::discard_new); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + REQUIRE(test_sink->msg_counter() < messages); + REQUIRE(tp->discard_counter() > 0); +} + +TEST_CASE("discard policy using factory ", "[async]") { + size_t queue_size = 4; + size_t messages = 1024; + spdlog::init_thread_pool(queue_size, 1); + + auto logger = spdlog::create_async_nb("as2"); + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + test_sink->set_delay(std::chrono::milliseconds(3)); + + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message"); + } + + REQUIRE(test_sink->msg_counter() < messages); + spdlog::drop_all(); +} + +TEST_CASE("flush", "[async]") { + auto test_sink = std::make_shared(); + size_t queue_size = 256; + size_t messages = 256; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + + logger->flush(); + } + // std::this_thread::sleep_for(std::chrono::milliseconds(250)); + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("async periodic flush", "[async]") { + auto logger = spdlog::create_async("as"); + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1700)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} + +TEST_CASE("tp->wait_empty() ", "[async]") { + auto test_sink = std::make_shared(); + test_sink->set_delay(std::chrono::milliseconds(5)); + size_t messages = 100; + + auto tp = std::make_shared(messages, 2); + auto logger = std::make_shared("as", test_sink, tp, + spdlog::async_overflow_policy::block); + for (size_t i = 0; i < messages; i++) { + logger->info("Hello message #{}", i); + } + logger->flush(); + tp.reset(); + + REQUIRE(test_sink->msg_counter() == messages); + REQUIRE(test_sink->flush_counter() == 1); +} + +TEST_CASE("multi threads", "[async]") { + auto test_sink = std::make_shared(); + size_t queue_size = 128; + size_t messages = 256; + size_t n_threads = 10; + { + auto tp = std::make_shared(queue_size, 1); + auto logger = std::make_shared("as", test_sink, tp, + spdlog::async_overflow_policy::block); + + std::vector threads; + for (size_t i = 0; i < n_threads; i++) { + threads.emplace_back([logger, messages] { + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + }); + logger->flush(); + } + + for (auto &t : threads) { + t.join(); + } + } + + REQUIRE(test_sink->msg_counter() == messages * n_threads); + REQUIRE(test_sink->flush_counter() == n_threads); +} + +TEST_CASE("to_file", "[async]") { + prepare_logdir(); + size_t messages = 1024; + size_t tp_threads = 1; + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + { + auto file_sink = std::make_shared(filename, true); + auto tp = std::make_shared(messages, tp_threads); + auto logger = + std::make_shared("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + } + + require_message_count(TEST_FILENAME, messages); + auto contents = file_contents(TEST_FILENAME); + using spdlog::details::os::default_eol; + REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol))); +} + +TEST_CASE("to_file multi-workers", "[async]") { + prepare_logdir(); + size_t messages = 1024 * 10; + size_t tp_threads = 10; + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + { + auto file_sink = std::make_shared(filename, true); + auto tp = std::make_shared(messages, tp_threads); + auto logger = + std::make_shared("as", std::move(file_sink), std::move(tp)); + + for (size_t j = 0; j < messages; j++) { + logger->info("Hello message #{}", j); + } + } + require_message_count(TEST_FILENAME, messages); +} + +TEST_CASE("bad_tp", "[async]") { + auto test_sink = std::make_shared(); + std::shared_ptr const empty_tp; + auto logger = std::make_shared("as", test_sink, empty_tp); + logger->info("Please throw an exception"); + REQUIRE(test_sink->msg_counter() == 0); +} diff --git a/lib/spdlog/tests/test_backtrace.cpp b/lib/spdlog/tests/test_backtrace.cpp new file mode 100644 index 0000000..4d78c0c --- /dev/null +++ b/lib/spdlog/tests/test_backtrace.cpp @@ -0,0 +1,73 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/async.h" + +TEST_CASE("bactrace1", "[bactrace]") { + using spdlog::sinks::test_sink_st; + auto test_sink = std::make_shared(); + size_t backtrace_size = 5; + + spdlog::logger logger("test-backtrace", test_sink); + logger.set_pattern("%v"); + logger.enable_backtrace(backtrace_size); + + logger.info("info message"); + for (int i = 0; i < 100; i++) logger.debug("debug message {}", i); + + REQUIRE(test_sink->lines().size() == 1); + REQUIRE(test_sink->lines()[0] == "info message"); + + logger.dump_backtrace(); + REQUIRE(test_sink->lines().size() == backtrace_size + 3); + REQUIRE(test_sink->lines()[1] == "****************** Backtrace Start ******************"); + REQUIRE(test_sink->lines()[2] == "debug message 95"); + REQUIRE(test_sink->lines()[3] == "debug message 96"); + REQUIRE(test_sink->lines()[4] == "debug message 97"); + REQUIRE(test_sink->lines()[5] == "debug message 98"); + REQUIRE(test_sink->lines()[6] == "debug message 99"); + REQUIRE(test_sink->lines()[7] == "****************** Backtrace End ********************"); +} + +TEST_CASE("bactrace-empty", "[bactrace]") { + using spdlog::sinks::test_sink_st; + auto test_sink = std::make_shared(); + size_t backtrace_size = 5; + + spdlog::logger logger("test-backtrace", test_sink); + logger.set_pattern("%v"); + logger.enable_backtrace(backtrace_size); + logger.dump_backtrace(); + REQUIRE(test_sink->lines().size() == 0); +} + +TEST_CASE("bactrace-async", "[bactrace]") { + using spdlog::sinks::test_sink_mt; + auto test_sink = std::make_shared(); + using spdlog::details::os::sleep_for_millis; + + size_t backtrace_size = 5; + + spdlog::init_thread_pool(120, 1); + auto logger = std::make_shared("test-bactrace-async", test_sink, + spdlog::thread_pool()); + logger->set_pattern("%v"); + logger->enable_backtrace(backtrace_size); + + logger->info("info message"); + for (int i = 0; i < 100; i++) logger->debug("debug message {}", i); + + sleep_for_millis(100); + REQUIRE(test_sink->lines().size() == 1); + REQUIRE(test_sink->lines()[0] == "info message"); + + logger->dump_backtrace(); + sleep_for_millis(100); // give time for the async dump to complete + REQUIRE(test_sink->lines().size() == backtrace_size + 3); + REQUIRE(test_sink->lines()[1] == "****************** Backtrace Start ******************"); + REQUIRE(test_sink->lines()[2] == "debug message 95"); + REQUIRE(test_sink->lines()[3] == "debug message 96"); + REQUIRE(test_sink->lines()[4] == "debug message 97"); + REQUIRE(test_sink->lines()[5] == "debug message 98"); + REQUIRE(test_sink->lines()[6] == "debug message 99"); + REQUIRE(test_sink->lines()[7] == "****************** Backtrace End ********************"); +} diff --git a/lib/spdlog/tests/test_bin_to_hex.cpp b/lib/spdlog/tests/test_bin_to_hex.cpp new file mode 100644 index 0000000..45fc9fa --- /dev/null +++ b/lib/spdlog/tests/test_bin_to_hex.cpp @@ -0,0 +1,97 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/fmt/bin_to_hex.h" + +TEST_CASE("to_hex", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE(ends_with(output, + "0000: 09 0a 0b 0c ff ff" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_upper", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{:X}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE(ends_with(output, + "0000: 09 0A 0B 0C FF FF" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_no_delimiter", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0xc, 0xff, 0xff}; + oss_logger.info("{:sX}", spdlog::to_hex(v)); + + auto output = oss.str(); + REQUIRE( + ends_with(output, "0000: 090A0B0CFFFF" + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_show_ascii", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 8)); + + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4BFFFF ...A.K.." + + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_different_size_per_line", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 10)); + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4BFFFF ...A.K.." + + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xs}", spdlog::to_hex(v, 10)); + REQUIRE(ends_with(oss.str(), + "0000: 090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xsa}", spdlog::to_hex(v, 6)); + REQUIRE(ends_with( + oss.str(), "0000: 090A0B410C4B ...A.K" + std::string(spdlog::details::os::default_eol) + + "0006: FFFF .." + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xs}", spdlog::to_hex(v, 6)); + REQUIRE(ends_with(oss.str(), "0000: 090A0B410C4B" + + std::string(spdlog::details::os::default_eol) + "0006: FFFF" + + std::string(spdlog::details::os::default_eol))); +} + +TEST_CASE("to_hex_no_ascii", "[to_hex]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("oss", oss_sink); + + std::vector v{9, 0xa, 0xb, 0x41, 0xc, 0x4b, 0xff, 0xff}; + oss_logger.info("{:Xs}", spdlog::to_hex(v, 8)); + + REQUIRE(ends_with(oss.str(), + "0000: 090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); + + oss_logger.info("{:Xsna}", spdlog::to_hex(v, 8)); + + REQUIRE( + ends_with(oss.str(), "090A0B410C4BFFFF" + std::string(spdlog::details::os::default_eol))); +} diff --git a/lib/spdlog/tests/test_cfg.cpp b/lib/spdlog/tests/test_cfg.cpp new file mode 100644 index 0000000..7dec94b --- /dev/null +++ b/lib/spdlog/tests/test_cfg.cpp @@ -0,0 +1,178 @@ + +#include "includes.h" +#include "test_sink.h" + +#include +#include + +using spdlog::cfg::load_argv_levels; +using spdlog::cfg::load_env_levels; +using spdlog::sinks::test_sink_st; + +TEST_CASE("env", "[cfg]") { + spdlog::drop("l1"); + auto l1 = spdlog::create("l1"); +#ifdef CATCH_PLATFORM_WINDOWS + _putenv_s("SPDLOG_LEVEL", "l1=warn"); +#else + setenv("SPDLOG_LEVEL", "l1=warn", 1); +#endif + load_env_levels(); + REQUIRE(l1->level() == spdlog::level::warn); + +#ifdef CATCH_PLATFORM_WINDOWS + _putenv_s("MYAPP_LEVEL", "l1=trace"); +#else + setenv("MYAPP_LEVEL", "l1=trace", 1); +#endif + load_env_levels("MYAPP_LEVEL"); + REQUIRE(l1->level() == spdlog::level::trace); + + spdlog::set_default_logger(spdlog::create("cfg-default")); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("argv1", "[cfg]") { + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=warn"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::info); +} + +TEST_CASE("argv2", "[cfg]") { + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=l1=warn,trace"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); +} + +TEST_CASE("argv3", "[cfg]") { + spdlog::set_level(spdlog::level::trace); + + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk_name=warn"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create("l1"); + REQUIRE(l1->level() == spdlog::level::trace); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); +} + +TEST_CASE("argv4", "[cfg]") { + spdlog::set_level(spdlog::level::info); + spdlog::drop("l1"); + const char *argv[] = {"ignore", "SPDLOG_LEVEL=junk"}; + load_argv_levels(2, argv); + auto l1 = spdlog::create("l1"); + REQUIRE(l1->level() == spdlog::level::info); +} + +TEST_CASE("argv5", "[cfg]") { + spdlog::set_level(spdlog::level::info); + spdlog::drop("l1"); + const char *argv[] = {"ignore", "ignore", "SPDLOG_LEVEL=l1=warn,trace"}; + load_argv_levels(3, argv); + auto l1 = spdlog::create("l1"); + REQUIRE(l1->level() == spdlog::level::warn); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::trace); + spdlog::set_level(spdlog::level::info); +} + +TEST_CASE("argv6", "[cfg]") { + spdlog::set_level(spdlog::level::err); + const char *argv[] = {""}; + load_argv_levels(1, argv); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::err); + spdlog::set_level(spdlog::level::info); +} + +TEST_CASE("argv7", "[cfg]") { + spdlog::set_level(spdlog::level::err); + const char *argv[] = {""}; + load_argv_levels(0, argv); + REQUIRE(spdlog::default_logger()->level() == spdlog::level::err); + 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("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("l1"); + l1->set_level(spdlog::level::warn); + auto l2 = spdlog::create("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("l1"); + auto l2 = spdlog::create("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("l1"); + auto l2 = spdlog::create("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("l1"); + auto l2 = spdlog::create("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); +} diff --git a/lib/spdlog/tests/test_circular_q.cpp b/lib/spdlog/tests/test_circular_q.cpp new file mode 100644 index 0000000..c8b02d3 --- /dev/null +++ b/lib/spdlog/tests/test_circular_q.cpp @@ -0,0 +1,50 @@ +#include "includes.h" +#include "spdlog/details/circular_q.h" + +using q_type = spdlog::details::circular_q; +TEST_CASE("test_size", "[circular_q]") { + const size_t q_size = 4; + q_type q(q_size); + REQUIRE(q.size() == 0); + REQUIRE(q.empty() == true); + for (size_t i = 0; i < q_size; i++) { + q.push_back(std::move(i)); + } + REQUIRE(q.size() == q_size); + q.push_back(999); + REQUIRE(q.size() == q_size); +} + +TEST_CASE("test_rolling", "[circular_q]") { + const size_t q_size = 4; + q_type q(q_size); + + for (size_t i = 0; i < q_size + 2; i++) { + q.push_back(std::move(i)); + } + + REQUIRE(q.size() == q_size); + + REQUIRE(q.front() == 2); + q.pop_front(); + + REQUIRE(q.front() == 3); + q.pop_front(); + + REQUIRE(q.front() == 4); + q.pop_front(); + + REQUIRE(q.front() == 5); + q.pop_front(); + + REQUIRE(q.empty()); + + q.push_back(6); + REQUIRE(q.front() == 6); +} + +TEST_CASE("test_empty", "[circular_q]") { + q_type q(0); + q.push_back(1); + REQUIRE(q.empty()); +} \ No newline at end of file diff --git a/lib/spdlog/tests/test_create_dir.cpp b/lib/spdlog/tests/test_create_dir.cpp new file mode 100644 index 0000000..1a21f01 --- /dev/null +++ b/lib/spdlog/tests/test_create_dir.cpp @@ -0,0 +1,144 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +using spdlog::details::os::create_dir; +using spdlog::details::os::path_exists; + +bool try_create_dir(const spdlog::filename_t &path, const spdlog::filename_t &normalized_path) { + auto rv = create_dir(path); + REQUIRE(rv == true); + return path_exists(normalized_path); +} + +TEST_CASE("create_dir", "[create_dir]") { + prepare_logdir(); + + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), + SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1/dir1"), + SPDLOG_FILENAME_T("test_logs/dir1/dir1"))); // test existing + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/dir1///dir2//"), + SPDLOG_FILENAME_T("test_logs/dir1/dir2"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("./test_logs/dir1/dir3"), + SPDLOG_FILENAME_T("test_logs/dir1/dir3"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs/../test_logs/dir1/dir4"), + SPDLOG_FILENAME_T("test_logs/dir1/dir4"))); + +#ifdef WIN32 + // test backslash folder separator + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir222"))); + REQUIRE(try_create_dir(SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"), + SPDLOG_FILENAME_T("test_logs\\dir1\\dir223\\"))); + REQUIRE(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 +} + +TEST_CASE("create_invalid_dir", "[create_dir]") { + REQUIRE(create_dir(SPDLOG_FILENAME_T("")) == false); + REQUIRE(create_dir(spdlog::filename_t{}) == false); +#ifdef __linux__ + REQUIRE(create_dir("/proc/spdlog-utest") == false); +#endif +} + +TEST_CASE("dir_name", "[create_dir]") { + using spdlog::details::os::dir_name; + REQUIRE(dir_name(SPDLOG_FILENAME_T("")).empty()); + REQUIRE(dir_name(SPDLOG_FILENAME_T("dir")).empty()); + +#ifdef WIN32 + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\\\)")) == SPDLOG_FILENAME_T(R"(dir\\)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir/file)")) == SPDLOG_FILENAME_T("dir")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(dir\file.txt\)")) == + SPDLOG_FILENAME_T(R"(dir\file.txt)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\dir)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(\\dir\file.txt)")) == SPDLOG_FILENAME_T(R"(\\dir)")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(..\file.txt)")) == SPDLOG_FILENAME_T("..")); + REQUIRE(dir_name(SPDLOG_FILENAME_T(R"(.\file.txt)")) == SPDLOG_FILENAME_T(".")); + 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(SPDLOG_FILENAME_T(R"(c://a/b/c/d/file.txt)")) == + SPDLOG_FILENAME_T(R"(c://a/b/c/d)")); +#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(".")); +} + +#ifdef _WIN32 + +// +// test windows cases when drive letter is given e.g. C:\\some-folder +// +#include +#include + +std::string get_full_path(const std::string &relative_folder_path) { + char full_path[MAX_PATH]; + + DWORD result = ::GetFullPathNameA(relative_folder_path.c_str(), MAX_PATH, full_path, nullptr); + // Return an empty string if failed to get full path + return result > 0 && result < MAX_PATH ? std::string(full_path) : std::string(); +} + +std::wstring get_full_path(const std::wstring &relative_folder_path) { + wchar_t full_path[MAX_PATH]; + DWORD result = ::GetFullPathNameW(relative_folder_path.c_str(), MAX_PATH, full_path, nullptr); + return result > 0 && result < MAX_PATH ? std::wstring(full_path) : std::wstring(); +} + +spdlog::filename_t::value_type find_non_existing_drive() { + for (char drive = 'A'; drive <= 'Z'; ++drive) { + std::string root_path = std::string(1, drive) + ":\\"; + UINT drive_type = GetDriveTypeA(root_path.c_str()); + if (drive_type == DRIVE_NO_ROOT_DIR) { + return static_cast(drive); + } + } + return '\0'; // No available drive found +} + +TEST_CASE("create_abs_path1", "[create_dir]") { + prepare_logdir(); + auto abs_path = get_full_path(SPDLOG_FILENAME_T("test_logs\\logdir1")); + REQUIRE(!abs_path.empty()); + REQUIRE(create_dir(abs_path) == true); +} + +TEST_CASE("create_abs_path2", "[create_dir]") { + prepare_logdir(); + auto abs_path = get_full_path(SPDLOG_FILENAME_T("test_logs/logdir2")); + REQUIRE(!abs_path.empty()); + REQUIRE(create_dir(abs_path) == true); +} + +TEST_CASE("non_existing_drive", "[create_dir]") { + prepare_logdir(); + spdlog::filename_t path; + + auto non_existing_drive = find_non_existing_drive(); + path += non_existing_drive; + path += SPDLOG_FILENAME_T(":\\"); + REQUIRE(create_dir(path) == false); + path += SPDLOG_FILENAME_T("subdir"); + REQUIRE(create_dir(path) == false); +} +// #endif // SPDLOG_WCHAR_FILENAMES +#endif // _WIN32 diff --git a/lib/spdlog/tests/test_custom_callbacks.cpp b/lib/spdlog/tests/test_custom_callbacks.cpp new file mode 100644 index 0000000..f145721 --- /dev/null +++ b/lib/spdlog/tests/test_custom_callbacks.cpp @@ -0,0 +1,37 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/sinks/callback_sink.h" +#include "spdlog/async.h" +#include "spdlog/common.h" + +TEST_CASE("custom_callback_logger", "[custom_callback_logger]") { + std::vector lines; + spdlog::pattern_formatter formatter; + auto callback_logger = + std::make_shared([&](const spdlog::details::log_msg &msg) { + spdlog::memory_buf_t formatted; + formatter.format(msg, formatted); + auto eol_len = strlen(spdlog::details::os::default_eol); + using diff_t = + typename std::iterator_traits::difference_type; + lines.emplace_back(formatted.begin(), formatted.end() - static_cast(eol_len)); + }); + std::shared_ptr test_sink(new spdlog::sinks::test_sink_st); + + spdlog::logger logger("test-callback", {callback_logger, test_sink}); + + logger.info("test message 1"); + logger.info("test message 2"); + logger.info("test message 3"); + + std::vector ref_lines = test_sink->lines(); + + REQUIRE(lines[0] == ref_lines[0]); + REQUIRE(lines[1] == ref_lines[1]); + REQUIRE(lines[2] == ref_lines[2]); + spdlog::drop_all(); +} diff --git a/lib/spdlog/tests/test_daily_logger.cpp b/lib/spdlog/tests/test_daily_logger.cpp new file mode 100644 index 0000000..4a691a3 --- /dev/null +++ b/lib/spdlog/tests/test_daily_logger.cpp @@ -0,0 +1,169 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#ifdef SPDLOG_USE_STD_FORMAT +using filename_memory_buf_t = std::basic_string; +#else +using filename_memory_buf_t = fmt::basic_memory_buffer; +#endif + +#ifdef SPDLOG_WCHAR_FILENAMES +std::string filename_buf_to_utf8string(const filename_memory_buf_t &w) { + spdlog::memory_buf_t buf; + spdlog::details::os::wstr_to_utf8buf(spdlog::wstring_view_t(w.data(), w.size()), buf); + return SPDLOG_BUF_TO_STRING(buf); +} +#else +std::string filename_buf_to_utf8string(const filename_memory_buf_t &w) { + return SPDLOG_BUF_TO_STRING(w); +} +#endif + +TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]") { + using sink_type = + spdlog::sinks::daily_file_sink; + + prepare_logdir(); + + // calculate filename (time based) + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); + std::tm tm = spdlog::details::os::localtime(); + filename_memory_buf_t w; + spdlog::fmt_lib::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("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + logger->flush(); + + require_message_count(filename_buf_to_utf8string(w), 10); +} + +struct custom_daily_file_name_calculator { + static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) { + return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, + now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday); + } +}; + +TEST_CASE("daily_logger with custom calculator", "[daily_logger]") { + using sink_type = spdlog::sinks::daily_file_sink; + + prepare_logdir(); + + // calculate filename (time based) + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); + std::tm tm = spdlog::details::os::localtime(); + filename_memory_buf_t w; + spdlog::fmt_lib::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("logger", basename, 0, 0); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + + require_message_count(filename_buf_to_utf8string(w), 10); +} + +/* + * File name calculations + */ + +TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 3); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3.txt")); +} + +TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated"), 3); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.3")); +} + +TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]") { + auto filename = + spdlog::sinks::rotating_file_sink_st::calc_filename(SPDLOG_FILENAME_T("rotated.txt"), 0); + REQUIRE(filename == SPDLOG_FILENAME_T("rotated.txt")); +} + +// regex supported only from gcc 4.9 and above +#if defined(_MSC_VER) || !(__GNUC__ <= 4 && __GNUC_MINOR__ < 9) + +#include + +TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]") { + // daily_YYYY-MM-DD_hh-mm.txt + 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 + std::basic_regex re( + 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 match; + REQUIRE(std::regex_match(filename, match, re)); +} +#endif + +TEST_CASE("daily_file_sink::daily_filename_format_calculator", "[daily_file_sink]") { + std::tm tm = spdlog::details::os::localtime(); + // example-YYYY-MM-DD.log + auto filename = spdlog::sinks::daily_filename_format_calculator::calc_filename( + SPDLOG_FILENAME_T("example-%Y-%m-%d.log"), tm); + + REQUIRE(filename == + spdlog::fmt_lib::format(SPDLOG_FILENAME_T("example-{:04d}-{:02d}-{:02d}.log"), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday)); +} + +/* Test removal of old files */ +static spdlog::details::log_msg create_msg(std::chrono::seconds offset) { + using spdlog::log_clock; + spdlog::details::log_msg msg{"test", spdlog::level::info, "Hello Message"}; + msg.time = log_clock::now() + offset; + return msg; +} + +static void test_rotate(int days_to_run, uint16_t max_days, uint16_t expected_n_files) { + using spdlog::log_clock; + using spdlog::details::log_msg; + using spdlog::sinks::daily_file_sink_st; + + prepare_logdir(); + + spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_rotate.txt"); + daily_file_sink_st sink{basename, 2, 30, true, max_days}; + + // simulate messages with 24 intervals + + for (int i = 0; i < days_to_run; i++) { + auto offset = std::chrono::seconds{24 * 3600 * i}; + sink.log(create_msg(offset)); + } + + REQUIRE(count_files("test_logs") == static_cast(expected_n_files)); +} + +TEST_CASE("daily_logger rotate", "[daily_file_sink]") { + int days_to_run = 1; + test_rotate(days_to_run, 0, 1); + test_rotate(days_to_run, 1, 1); + test_rotate(days_to_run, 3, 1); + test_rotate(days_to_run, 10, 1); + + days_to_run = 10; + test_rotate(days_to_run, 0, 10); + test_rotate(days_to_run, 1, 1); + test_rotate(days_to_run, 3, 3); + test_rotate(days_to_run, 9, 9); + test_rotate(days_to_run, 10, 10); + test_rotate(days_to_run, 11, 10); + test_rotate(days_to_run, 20, 10); +} diff --git a/lib/spdlog/tests/test_dup_filter.cpp b/lib/spdlog/tests/test_dup_filter.cpp new file mode 100644 index 0000000..78e22be --- /dev/null +++ b/lib/spdlog/tests/test_dup_filter.cpp @@ -0,0 +1,83 @@ +#include "includes.h" +#include "spdlog/sinks/dup_filter_sink.h" +#include "test_sink.h" + +TEST_CASE("dup_filter_test1", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{5}}; + auto test_sink = std::make_shared(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + } + + REQUIRE(test_sink->msg_counter() == 1); +} + +TEST_CASE("dup_filter_test2", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{0}}; + auto test_sink = std::make_shared(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + REQUIRE(test_sink->msg_counter() == 10); +} + +TEST_CASE("dup_filter_test3", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_st; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_st dup_sink{std::chrono::seconds{1}}; + auto test_sink = std::make_shared(); + dup_sink.add_sink(test_sink); + + for (int i = 0; i < 10; i++) { + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message2"}); + } + + REQUIRE(test_sink->msg_counter() == 20); +} + +TEST_CASE("dup_filter_test4", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_mt; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_mt dup_sink{std::chrono::milliseconds{10}}; + auto test_sink = std::make_shared(); + dup_sink.add_sink(test_sink); + + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message"}); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message"}); + REQUIRE(test_sink->msg_counter() == 2); +} + +TEST_CASE("dup_filter_test5", "[dup_filter_sink]") { + using spdlog::sinks::dup_filter_sink_mt; + using spdlog::sinks::test_sink_mt; + + dup_filter_sink_mt dup_sink{std::chrono::seconds{5}}; + auto test_sink = std::make_shared(); + test_sink->set_pattern("%v"); + 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"}); + dup_sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "message1"}); + 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->lines()[1] == "Skipped 2 duplicate messages.."); +} diff --git a/lib/spdlog/tests/test_errors.cpp b/lib/spdlog/tests/test_errors.cpp new file mode 100644 index 0000000..1c24cab --- /dev/null +++ b/lib/spdlog/tests/test_errors.cpp @@ -0,0 +1,112 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#include + +#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 { +protected: + void sink_it_(const spdlog::details::log_msg &) final { + throw std::runtime_error("some error happened during log"); + } + + void flush_() final { throw std::runtime_error("some error happened during flush"); } +}; +struct custom_ex {}; + +#if !defined(SPDLOG_USE_STD_FORMAT) // std format doesn't fully support runtime strings +TEST_CASE("default_error_handler", "[errors]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create("test-error", filename, true); + logger->set_pattern("%v"); + logger->info(SPDLOG_FMT_RUNTIME("Test message {} {}"), 1); + logger->info("Test message {}", 2); + logger->flush(); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == spdlog::fmt_lib::format("Test message 2{}", default_eol)); + REQUIRE(count_lines(SIMPLE_LOG) == 1); +} + +TEST_CASE("custom_error_handler", "[errors]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + auto logger = spdlog::create("logger", filename, true); + logger->flush_on(spdlog::level::info); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + logger->info("Good message #1"); + + REQUIRE_THROWS_AS(logger->info(SPDLOG_FMT_RUNTIME("Bad format msg {} {}"), "xxx"), custom_ex); + logger->info("Good message #2"); + require_message_count(SIMPLE_LOG, 2); +} +#endif + +TEST_CASE("default_error_handler2", "[errors]") { + spdlog::drop_all(); + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); +} + +TEST_CASE("flush_error_handler", "[errors]") { + spdlog::drop_all(); + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); + REQUIRE_THROWS_AS(logger->flush(), custom_ex); +} + +#if !defined(SPDLOG_USE_STD_FORMAT) +TEST_CASE("async_error_handler", "[errors]") { + prepare_logdir(); + std::string err_msg("log failed with some msg"); + + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_ASYNC_LOG); + { + spdlog::init_thread_pool(128, 1); + auto logger = + spdlog::create_async("logger", filename, true); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("test_logs/custom_err.txt"); + if (!ofs) { + throw std::runtime_error("Failed open test_logs/custom_err.txt"); + } + ofs << err_msg; + }); + logger->info("Good message #1"); + logger->info(SPDLOG_FMT_RUNTIME("Bad format msg {} {}"), "xxx"); + logger->info("Good message #2"); + spdlog::drop("logger"); // force logger to drain the queue and shutdown + } + spdlog::init_thread_pool(128, 1); + require_message_count(SIMPLE_ASYNC_LOG, 2); + REQUIRE(file_contents("test_logs/custom_err.txt") == err_msg); +} +#endif + +// Make sure async error handler is executed +TEST_CASE("async_error_handler2", "[errors]") { + prepare_logdir(); + std::string err_msg("This is async handler error message"); + { + spdlog::details::os::create_dir(SPDLOG_FILENAME_T("test_logs")); + spdlog::init_thread_pool(128, 1); + auto logger = spdlog::create_async("failed_logger"); + logger->set_error_handler([=](const std::string &) { + std::ofstream ofs("test_logs/custom_err2.txt"); + if (!ofs) throw std::runtime_error("Failed open test_logs/custom_err2.txt"); + ofs << err_msg; + }); + logger->info("Hello failure"); + spdlog::drop("failed_logger"); // force logger to drain the queue and shutdown + } + + spdlog::init_thread_pool(128, 1); + REQUIRE(file_contents("test_logs/custom_err2.txt") == err_msg); +} diff --git a/lib/spdlog/tests/test_eventlog.cpp b/lib/spdlog/tests/test_eventlog.cpp new file mode 100644 index 0000000..dca9896 --- /dev/null +++ b/lib/spdlog/tests/test_eventlog.cpp @@ -0,0 +1,75 @@ +#if _WIN32 + +#include "includes.h" +#include "test_sink.h" + +#include "spdlog/sinks/win_eventlog_sink.h" + +static const LPCSTR TEST_SOURCE = "spdlog_test"; + +static void test_single_print(std::function do_log, + std::string const &expected_contents, + WORD expected_ev_type) { + using namespace std::chrono; + do_log(expected_contents); + const auto expected_time_generated = + duration_cast(system_clock::now().time_since_epoch()).count(); + + struct handle_t { + HANDLE handle_; + + ~handle_t() { + if (handle_) { + REQUIRE(CloseEventLog(handle_)); + } + } + } event_log{::OpenEventLogA(nullptr, TEST_SOURCE)}; + + REQUIRE(event_log.handle_); + + DWORD read_bytes{}, size_needed{}; + auto ok = ::ReadEventLogA(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, + 0, &read_bytes, 0, &read_bytes, &size_needed); + REQUIRE(!ok); + REQUIRE(::GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + std::vector record_buffer(size_needed); + PEVENTLOGRECORD record = (PEVENTLOGRECORD)record_buffer.data(); + + ok = ::ReadEventLogA(event_log.handle_, EVENTLOG_SEQUENTIAL_READ | EVENTLOG_BACKWARDS_READ, 0, + record, size_needed, &read_bytes, &size_needed); + REQUIRE(ok); + + REQUIRE(record->NumStrings == 1); + REQUIRE(record->EventType == expected_ev_type); + REQUIRE((expected_time_generated - record->TimeGenerated) <= 3u); + + std::string message_in_log(((char *)record + record->StringOffset)); + REQUIRE(message_in_log == expected_contents + spdlog::details::os::default_eol); +} + +TEST_CASE("eventlog", "[eventlog]") { + using namespace spdlog; + + auto test_sink = std::make_shared(TEST_SOURCE); + + spdlog::logger test_logger("eventlog", test_sink); + test_logger.set_level(level::trace); + + test_sink->set_pattern("%v"); + + test_single_print([&test_logger](std::string const &msg) { test_logger.trace(msg); }, + "my trace message", EVENTLOG_SUCCESS); + test_single_print([&test_logger](std::string const &msg) { test_logger.debug(msg); }, + "my debug message", EVENTLOG_SUCCESS); + test_single_print([&test_logger](std::string const &msg) { test_logger.info(msg); }, + "my info message", EVENTLOG_INFORMATION_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.warn(msg); }, + "my warn message", EVENTLOG_WARNING_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.error(msg); }, + "my error message", EVENTLOG_ERROR_TYPE); + test_single_print([&test_logger](std::string const &msg) { test_logger.critical(msg); }, + "my critical message", EVENTLOG_ERROR_TYPE); +} + +#endif //_WIN32 diff --git a/lib/spdlog/tests/test_file_helper.cpp b/lib/spdlog/tests/test_file_helper.cpp new file mode 100644 index 0000000..56ee75e --- /dev/null +++ b/lib/spdlog/tests/test_file_helper.cpp @@ -0,0 +1,169 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#define TEST_FILENAME "test_logs/file_helper_test.txt" + +using spdlog::details::file_helper; + +static void write_with_helper(file_helper &helper, size_t howmany) { + spdlog::memory_buf_t formatted; + spdlog::fmt_lib::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1')); + helper.write(formatted); + helper.flush(); +} + +TEST_CASE("file_helper_filename", "[file_helper::filename()]") { + prepare_logdir(); + + file_helper helper; + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + helper.open(target_filename); + REQUIRE(helper.filename() == target_filename); +} + +TEST_CASE("file_helper_size", "[file_helper::size()]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + size_t expected_size = 123; + { + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(static_cast(helper.size()) == expected_size); + } + REQUIRE(get_filesize(TEST_FILENAME) == expected_size); +} + +TEST_CASE("file_helper_reopen", "[file_helper::reopen()]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, 12); + REQUIRE(helper.size() == 12); + helper.reopen(true); + REQUIRE(helper.size() == 0); +} + +TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + size_t expected_size = 14; + file_helper helper; + helper.open(target_filename); + write_with_helper(helper, expected_size); + REQUIRE(helper.size() == expected_size); + helper.reopen(false); + REQUIRE(helper.size() == expected_size); +} + +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 expected_base(expect_base); + spdlog::filename_t expected_ext(expect_ext); + + spdlog::filename_t basename; + spdlog::filename_t ext; + std::tie(basename, ext) = file_helper::split_by_extension(filename); + REQUIRE(basename == expected_base); + REQUIRE(ext == expected_ext); +} + +TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]") { + test_split_ext(SPDLOG_FILENAME_T("mylog.txt"), SPDLOG_FILENAME_T("mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".mylog.txt"), SPDLOG_FILENAME_T(".mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T(".mylog"), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), SPDLOG_FILENAME_T("/aaa/bb.d/mylog"), + SPDLOG_FILENAME_T("")); + 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(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog.txt"), + SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), SPDLOG_FILENAME_T("aaa/bbb/ccc/mylog."), + SPDLOG_FILENAME_T("")); + 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(SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog.txt"), + SPDLOG_FILENAME_T("/aaa/bbb/ccc/mylog"), SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), + SPDLOG_FILENAME_T("/aaa/bbb/ccc/.mylog"), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("../mylog.txt"), SPDLOG_FILENAME_T("../mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt"), SPDLOG_FILENAME_T(".././mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(".././mylog.txt/xxx"), SPDLOG_FILENAME_T(".././mylog.txt/xxx"), + SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("/mylog.txt"), SPDLOG_FILENAME_T("/mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T("//mylog.txt"), SPDLOG_FILENAME_T("//mylog"), + SPDLOG_FILENAME_T(".txt")); + test_split_ext(SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T(""), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("")); + test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt")); +} + +TEST_CASE("file_event_handlers", "[file_helper]") { + enum class flags { before_open, after_open, before_close, after_close }; + prepare_logdir(); + + spdlog::filename_t test_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + // define event handles that update vector of flags when called + std::vector events; + spdlog::file_event_handlers handlers; + handlers.before_open = [&](spdlog::filename_t filename) { + REQUIRE(filename == test_filename); + events.push_back(flags::before_open); + }; + handlers.after_open = [&](spdlog::filename_t filename, std::FILE *fstream) { + REQUIRE(filename == test_filename); + REQUIRE(fstream); + fputs("after_open\n", fstream); + events.push_back(flags::after_open); + }; + handlers.before_close = [&](spdlog::filename_t filename, std::FILE *fstream) { + REQUIRE(filename == test_filename); + REQUIRE(fstream); + fputs("before_close\n", fstream); + events.push_back(flags::before_close); + }; + handlers.after_close = [&](spdlog::filename_t filename) { + REQUIRE(filename == test_filename); + events.push_back(flags::after_close); + }; + { + spdlog::details::file_helper helper{handlers}; + REQUIRE(events.empty()); + + helper.open(test_filename); + REQUIRE(events == std::vector{flags::before_open, flags::after_open}); + + events.clear(); + helper.close(); + REQUIRE(events == std::vector{flags::before_close, flags::after_close}); + REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n"); + + helper.reopen(true); + events.clear(); + } + // make sure that the file_helper destructor calls the close callbacks if needed + REQUIRE(events == std::vector{flags::before_close, flags::after_close}); + REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n"); +} + +TEST_CASE("file_helper_open", "[file_helper]") { + prepare_logdir(); + spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME); + file_helper helper; + helper.open(target_filename); + helper.close(); + + target_filename += SPDLOG_FILENAME_T("/invalid"); + REQUIRE_THROWS_AS(helper.open(target_filename), spdlog::spdlog_ex); +} diff --git a/lib/spdlog/tests/test_file_logging.cpp b/lib/spdlog/tests/test_file_logging.cpp new file mode 100644 index 0000000..e3155ef --- /dev/null +++ b/lib/spdlog/tests/test_file_logging.cpp @@ -0,0 +1,187 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" + +#define SIMPLE_LOG "test_logs/simple_log" +#define ROTATING_LOG "test_logs/rotating_log" + +TEST_CASE("simple_file_logger", "[simple_logger]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + logger->flush(); + require_message_count(SIMPLE_LOG, 2); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == + spdlog::fmt_lib::format("Test message 1{}Test message 2{}", default_eol, default_eol)); +} + +TEST_CASE("flush_on", "[flush_on]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::info); + logger->trace("Should not be flushed"); + REQUIRE(count_lines(SIMPLE_LOG) == 0); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + + require_message_count(SIMPLE_LOG, 3); + using spdlog::details::os::default_eol; + REQUIRE(file_contents(SIMPLE_LOG) == + spdlog::fmt_lib::format("Should not be flushed{}Test message 1{}Test message 2{}", + default_eol, default_eol, default_eol)); +} + +TEST_CASE("simple_file_logger", "[truncate]") { + prepare_logdir(); + const spdlog::filename_t filename = SPDLOG_FILENAME_T(SIMPLE_LOG); + const bool truncate = true; + const auto sink = std::make_shared(filename, truncate); + const auto logger = std::make_shared("simple_file_logger", sink); + + logger->info("Test message {}", 3.14); + logger->info("Test message {}", 2.71); + logger->flush(); + REQUIRE(count_lines(SIMPLE_LOG) == 2); + + sink->truncate(); + REQUIRE(count_lines(SIMPLE_LOG) == 0); + + logger->info("Test message {}", 6.28); + logger->flush(); + REQUIRE(count_lines(SIMPLE_LOG) == 1); +} + +TEST_CASE("rotating_file_logger1", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 0); + + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + + logger->flush(); + require_message_count(ROTATING_LOG, 10); +} + +TEST_CASE("rotating_file_logger2", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + + { + // make an initial logger to create the first output file + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + // drop causes the logger destructor to be called, which is required so the + // next logger can rename the first output file. + spdlog::drop(logger->name()); + } + auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); + for (int i = 0; i < 10; ++i) { + logger->info("Test message {}", i); + } + logger->flush(); + require_message_count(ROTATING_LOG, 10); + + for (int i = 0; i < 1000; i++) { + logger->info("Test message {}", i); + } + + logger->flush(); + REQUIRE(get_filesize(ROTATING_LOG) <= max_size); + REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size); +} + +// test that passing max_size=0 throws +TEST_CASE("rotating_file_logger3", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 0; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), + spdlog::spdlog_ex); +} + +// test on-demand rotation of logs +TEST_CASE("rotating_file_logger4", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 1024 * 10; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto sink = std::make_shared(basename, max_size, 2); + auto logger = std::make_shared("rotating_sink_logger", sink); + + logger->info("Test message - pre-rotation"); + logger->flush(); + + sink->rotate_now(); + + logger->info("Test message - post-rotation"); + logger->flush(); + + REQUIRE(get_filesize(ROTATING_LOG) > 0); + REQUIRE(get_filesize(ROTATING_LOG ".1") > 0); +} + +// test changing the max size of the rotating file sink +TEST_CASE("rotating_file_logger5", "[rotating_logger]") { + prepare_logdir(); + size_t max_size = 5 * 1024; + size_t max_files = 2; + spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG); + auto sink = + std::make_shared(basename, max_size, max_files); + auto logger = std::make_shared("rotating_sink_logger", sink); + logger->set_pattern("%v"); + + REQUIRE(sink->get_max_size() == max_size); + REQUIRE(sink->get_max_files() == max_files); + max_size = 7 * 1024; + max_files = 3; + + sink->set_max_size(max_size); + sink->set_max_files(max_files); + REQUIRE(sink->get_max_size() == max_size); + REQUIRE(sink->get_max_files() == max_files); + + const auto message = std::string(200, 'x'); + assert(message.size() < max_size); + const auto n_messages = max_files * max_size / message.size(); + for (size_t i = 0; i < n_messages; ++i) { + logger->info(message); + } + logger.reset(); // force flush and close the file + + // validate that the files were rotated correctly with the new max size and max files + for (size_t i = 0; i <= max_files; i++) { + // calc filenames + // e.g. rotating_log, rotating_log.0 rotating_log.1, rotating_log.2, etc. + std::ostringstream oss; + oss << ROTATING_LOG; + if (i > 0) { + oss << '.' << i; + } + const auto filename = oss.str(); + const auto filesize = get_filesize(filename); + REQUIRE(filesize <= max_size); + if (i > 0) { + REQUIRE(filesize >= max_size - message.size() - 2); + } + } +} diff --git a/lib/spdlog/tests/test_fmt_helper.cpp b/lib/spdlog/tests/test_fmt_helper.cpp new file mode 100644 index 0000000..31b9306 --- /dev/null +++ b/lib/spdlog/tests/test_fmt_helper.cpp @@ -0,0 +1,82 @@ + +#include "includes.h" + +using spdlog::memory_buf_t; +using spdlog::details::to_string_view; + +void test_pad2(int n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad2(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad3(uint32_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad3(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad6(std::size_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad6(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +void test_pad9(std::size_t n, const char *expected) { + memory_buf_t buf; + spdlog::details::fmt_helper::pad9(n, buf); + + REQUIRE(to_string_view(buf) == expected); +} + +TEST_CASE("pad2", "[fmt_helper]") { + test_pad2(0, "00"); + test_pad2(3, "03"); + test_pad2(10, "10"); + test_pad2(23, "23"); + test_pad2(99, "99"); + test_pad2(100, "100"); + test_pad2(123, "123"); + test_pad2(1234, "1234"); + test_pad2(-5, "-5"); +} + +TEST_CASE("pad3", "[fmt_helper]") { + test_pad3(0, "000"); + test_pad3(3, "003"); + test_pad3(10, "010"); + test_pad3(23, "023"); + test_pad3(99, "099"); + test_pad3(100, "100"); + test_pad3(123, "123"); + test_pad3(999, "999"); + test_pad3(1000, "1000"); + test_pad3(1234, "1234"); +} + +TEST_CASE("pad6", "[fmt_helper]") { + test_pad6(0, "000000"); + test_pad6(3, "000003"); + test_pad6(23, "000023"); + test_pad6(123, "000123"); + test_pad6(1234, "001234"); + test_pad6(12345, "012345"); + test_pad6(123456, "123456"); +} + +TEST_CASE("pad9", "[fmt_helper]") { + test_pad9(0, "000000000"); + test_pad9(3, "000000003"); + test_pad9(23, "000000023"); + test_pad9(123, "000000123"); + test_pad9(1234, "000001234"); + test_pad9(12345, "000012345"); + test_pad9(123456, "000123456"); + test_pad9(1234567, "001234567"); + test_pad9(12345678, "012345678"); + test_pad9(123456789, "123456789"); + test_pad9(1234567891, "1234567891"); +} diff --git a/lib/spdlog/tests/test_macros.cpp b/lib/spdlog/tests/test_macros.cpp new file mode 100644 index 0000000..ce267ef --- /dev/null +++ b/lib/spdlog/tests/test_macros.cpp @@ -0,0 +1,53 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ + +#include "includes.h" + +#if SPDLOG_ACTIVE_LEVEL != SPDLOG_LEVEL_DEBUG +#error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG" +#endif + +#define TEST_FILENAME "test_logs/simple_log" + +TEST_CASE("debug and trace w/o format string", "[macros]") { + prepare_logdir(); + spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + + SPDLOG_LOGGER_TRACE(logger, "Test message 1"); + SPDLOG_LOGGER_DEBUG(logger, "Test message 2"); + logger->flush(); + + using spdlog::details::os::default_eol; + REQUIRE(ends_with(file_contents(TEST_FILENAME), + spdlog::fmt_lib::format("Test message 2{}", default_eol))); + REQUIRE(count_lines(TEST_FILENAME) == 1); + + auto orig_default_logger = spdlog::default_logger(); + spdlog::set_default_logger(logger); + + SPDLOG_TRACE("Test message 3"); + SPDLOG_DEBUG("Test message {}", 4); + logger->flush(); + + require_message_count(TEST_FILENAME, 2); + REQUIRE(ends_with(file_contents(TEST_FILENAME), + spdlog::fmt_lib::format("Test message 4{}", default_eol))); + spdlog::set_default_logger(std::move(orig_default_logger)); +} + +TEST_CASE("disable param evaluation", "[macros]") { + SPDLOG_TRACE("Test message {}", throw std::runtime_error("Should not be evaluated")); +} + +TEST_CASE("pass logger pointer", "[macros]") { + auto logger = spdlog::create("refmacro"); + auto &ref = *logger; + SPDLOG_LOGGER_TRACE(&ref, "Test message 1"); + SPDLOG_LOGGER_DEBUG(&ref, "Test message 2"); +} diff --git a/lib/spdlog/tests/test_misc.cpp b/lib/spdlog/tests/test_misc.cpp new file mode 100644 index 0000000..ff32fed --- /dev/null +++ b/lib/spdlog/tests/test_misc.cpp @@ -0,0 +1,224 @@ +#ifdef _WIN32 // to prevent fopen warning on windows +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "includes.h" +#include "test_sink.h" + +template +std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info) { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::logger oss_logger("oss", oss_sink); + oss_logger.set_level(logger_level); + oss_logger.set_pattern("%v"); + oss_logger.info(what); + + return oss.str().substr(0, oss.str().length() - strlen(spdlog::details::os::default_eol)); +} + +TEST_CASE("basic_logging ", "[basic_logging]") { + // const char + REQUIRE(log_info("Hello") == "Hello"); + REQUIRE(log_info("").empty()); + + // std::string + REQUIRE(log_info(std::string("Hello")) == "Hello"); + REQUIRE(log_info(std::string()).empty()); + + // Numbers + REQUIRE(log_info(5) == "5"); + REQUIRE(log_info(5.6) == "5.6"); + + // User defined class + // REQUIRE(log_info(some_logged_class("some_val")) == "some_val"); +} + +TEST_CASE("log_levels", "[log_levels]") { + REQUIRE(log_info("Hello", spdlog::level::err).empty()); + REQUIRE(log_info("Hello", spdlog::level::critical).empty()); + REQUIRE(log_info("Hello", spdlog::level::info) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::debug) == "Hello"); + REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello"); +} + +TEST_CASE("level_to_string_view", "[convert_to_string_view]") { + REQUIRE(spdlog::level::to_string_view(spdlog::level::trace) == "trace"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::debug) == "debug"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::info) == "info"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::warn) == "warning"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::err) == "error"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::critical) == "critical"); + REQUIRE(spdlog::level::to_string_view(spdlog::level::off) == "off"); +} + +TEST_CASE("to_short_c_str", "[convert_to_short_c_str]") { + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::trace)) == "T"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::debug)) == "D"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::info)) == "I"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::warn)) == "W"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::err)) == "E"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::critical)) == "C"); + REQUIRE(std::string(spdlog::level::to_short_c_str(spdlog::level::off)) == "O"); +} + +TEST_CASE("to_level_enum", "[convert_to_level_enum]") { + REQUIRE(spdlog::level::from_str("trace") == spdlog::level::trace); + REQUIRE(spdlog::level::from_str("debug") == spdlog::level::debug); + REQUIRE(spdlog::level::from_str("info") == spdlog::level::info); + REQUIRE(spdlog::level::from_str("warning") == spdlog::level::warn); + REQUIRE(spdlog::level::from_str("warn") == spdlog::level::warn); + REQUIRE(spdlog::level::from_str("error") == spdlog::level::err); + REQUIRE(spdlog::level::from_str("critical") == spdlog::level::critical); + REQUIRE(spdlog::level::from_str("off") == spdlog::level::off); + REQUIRE(spdlog::level::from_str("null") == spdlog::level::off); +} + +TEST_CASE("periodic flush", "[periodic_flush]") { + using spdlog::sinks::test_sink_mt; + auto logger = spdlog::create("periodic_flush"); + auto test_sink = std::static_pointer_cast(logger->sinks()[0]); + + spdlog::flush_every(std::chrono::seconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1250)); + REQUIRE(test_sink->flush_counter() == 1); + spdlog::flush_every(std::chrono::seconds(0)); + spdlog::drop_all(); +} + +TEST_CASE("clone-logger", "[clone]") { + using spdlog::sinks::test_sink_mt; + auto test_sink = std::make_shared(); + auto logger = std::make_shared("orig", test_sink); + logger->set_pattern("%v"); + auto cloned = logger->clone("clone"); + + REQUIRE(cloned->name() == "clone"); + REQUIRE(logger->sinks() == cloned->sinks()); + REQUIRE(logger->level() == cloned->level()); + REQUIRE(logger->flush_level() == cloned->flush_level()); + logger->info("Some message 1"); + cloned->info("Some message 2"); + + REQUIRE(test_sink->lines().size() == 2); + REQUIRE(test_sink->lines()[0] == "Some message 1"); + REQUIRE(test_sink->lines()[1] == "Some message 2"); + + spdlog::drop_all(); +} + +TEST_CASE("clone async", "[clone]") { + using spdlog::sinks::test_sink_mt; + spdlog::init_thread_pool(4, 1); + auto test_sink = std::make_shared(); + auto logger = std::make_shared("orig", test_sink, spdlog::thread_pool()); + logger->set_pattern("%v"); + auto cloned = logger->clone("clone"); + + REQUIRE(cloned->name() == "clone"); + REQUIRE(logger->sinks() == cloned->sinks()); + REQUIRE(logger->level() == cloned->level()); + REQUIRE(logger->flush_level() == cloned->flush_level()); + + logger->info("Some message 1"); + cloned->info("Some message 2"); + + spdlog::details::os::sleep_for_millis(100); + + REQUIRE(test_sink->lines().size() == 2); + REQUIRE(test_sink->lines()[0] == "Some message 1"); + REQUIRE(test_sink->lines()[1] == "Some message 2"); + + spdlog::drop_all(); +} + +TEST_CASE("default logger API", "[default logger]") { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::set_default_logger(std::make_shared("oss", oss_sink)); + spdlog::set_pattern("*** %v"); + + spdlog::default_logger()->set_level(spdlog::level::trace); + spdlog::trace("hello trace"); + REQUIRE(oss.str() == "*** hello trace" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::debug("hello debug"); + REQUIRE(oss.str() == "*** hello debug" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::info("Hello"); + REQUIRE(oss.str() == "*** Hello" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::warn("Hello again {}", 2); + REQUIRE(oss.str() == "*** Hello again 2" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::error(123); + REQUIRE(oss.str() == "*** 123" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::critical(std::string("some string")); + REQUIRE(oss.str() == "*** some string" + std::string(spdlog::details::os::default_eol)); + + oss.str(""); + spdlog::set_level(spdlog::level::info); + spdlog::debug("should not be logged"); + REQUIRE(oss.str().empty()); + spdlog::drop_all(); + spdlog::set_pattern("%v"); +} + +#if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) +TEST_CASE("utf8 to utf16 conversion using windows api", "[windows utf]") { + spdlog::wmemory_buf_t buffer; + + spdlog::details::os::utf8_to_wstrbuf("", buffer); + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"")); + + spdlog::details::os::utf8_to_wstrbuf("abc", buffer); + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc")); + + spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence. + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd(")); + + spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93", + buffer); // "Neko" in hiragana. + REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053")); +} +#endif + +struct auto_closer { + FILE* fp = nullptr; + explicit auto_closer(FILE* f) + : fp(f) {} + auto_closer(const auto_closer&) = delete; + auto_closer& operator=(const auto_closer&) = delete; + ~auto_closer() { + if (fp != nullptr) (void)std::fclose(fp); + } +}; + +TEST_CASE("os::fwrite_bytes", "[os]") { + using spdlog::details::os::create_dir; + using spdlog::details::os::fwrite_bytes; + const char* filename = "log_tests/test_fwrite_bytes.txt"; + const char* msg = "hello"; + prepare_logdir(); + REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true); + { + auto_closer closer(std::fopen(filename, "wb")); + REQUIRE(closer.fp != nullptr); + REQUIRE(fwrite_bytes(msg, std::strlen(msg), closer.fp) == true); + REQUIRE(fwrite_bytes(msg, 0, closer.fp) == true); + std::fflush(closer.fp); + REQUIRE(spdlog::details::os::filesize(closer.fp) == 5); + } + // fwrite_bytes should return false on write failure + auto_closer closer(std::fopen(filename, "r")); + REQUIRE(closer.fp != nullptr); + REQUIRE_FALSE(fwrite_bytes("Hello", 5, closer.fp)); +} diff --git a/lib/spdlog/tests/test_mpmc_q.cpp b/lib/spdlog/tests/test_mpmc_q.cpp new file mode 100644 index 0000000..bc7a37d --- /dev/null +++ b/lib/spdlog/tests/test_mpmc_q.cpp @@ -0,0 +1,114 @@ +#include "includes.h" + +using std::chrono::milliseconds; +using test_clock = std::chrono::high_resolution_clock; + +static milliseconds millis_from(const test_clock::time_point &tp0) { + return std::chrono::duration_cast(test_clock::now() - tp0); +} +TEST_CASE("dequeue-empty-nowait", "[mpmc_blocking_q]") { + size_t q_size = 100; + milliseconds tolerance_wait(20); + spdlog::details::mpmc_blocking_queue q(q_size); + int popped_item = 0; + + auto start = test_clock::now(); + auto rv = q.dequeue_for(popped_item, milliseconds::zero()); + auto delta_ms = millis_from(start); + + REQUIRE(rv == false); + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms <= tolerance_wait); +} + +TEST_CASE("dequeue-empty-wait", "[mpmc_blocking_q]") { + size_t q_size = 100; + milliseconds wait_ms(250); + milliseconds tolerance_wait(250); + + spdlog::details::mpmc_blocking_queue q(q_size); + int popped_item = 0; + auto start = test_clock::now(); + auto rv = q.dequeue_for(popped_item, wait_ms); + auto delta_ms = millis_from(start); + + REQUIRE(rv == false); + + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms >= wait_ms - tolerance_wait); + REQUIRE(delta_ms <= wait_ms + tolerance_wait); +} + +TEST_CASE("dequeue-full-nowait", "[mpmc_blocking_q]") { + spdlog::details::mpmc_blocking_queue q(1); + q.enqueue(42); + + int item = 0; + q.dequeue_for(item, milliseconds::zero()); + REQUIRE(item == 42); +} + +TEST_CASE("dequeue-full-wait", "[mpmc_blocking_q]") { + spdlog::details::mpmc_blocking_queue q(1); + q.enqueue(42); + + int item = 0; + q.dequeue(item); + REQUIRE(item == 42); +} + +TEST_CASE("enqueue_nowait", "[mpmc_blocking_q]") { + size_t q_size = 1; + spdlog::details::mpmc_blocking_queue q(q_size); + milliseconds tolerance_wait(10); + + q.enqueue(1); + REQUIRE(q.overrun_counter() == 0); + + auto start = test_clock::now(); + q.enqueue_nowait(2); + auto delta_ms = millis_from(start); + + INFO("Delta " << delta_ms.count() << " millis"); + REQUIRE(delta_ms <= tolerance_wait); + REQUIRE(q.overrun_counter() == 1); +} + +TEST_CASE("bad_queue", "[mpmc_blocking_q]") { + size_t q_size = 0; + spdlog::details::mpmc_blocking_queue q(q_size); + q.enqueue_nowait(1); + REQUIRE(q.overrun_counter() == 1); + int i = 0; + REQUIRE(q.dequeue_for(i, milliseconds(0)) == false); +} + +TEST_CASE("empty_queue", "[mpmc_blocking_q]") { + size_t q_size = 10; + spdlog::details::mpmc_blocking_queue q(q_size); + int i = 0; + REQUIRE(q.dequeue_for(i, milliseconds(10)) == false); +} + +TEST_CASE("full_queue", "[mpmc_blocking_q]") { + size_t q_size = 100; + spdlog::details::mpmc_blocking_queue q(q_size); + for (int i = 0; i < static_cast(q_size); i++) { + q.enqueue(i + 0); // i+0 to force rvalue and avoid tidy warnings on the same time if we + // std::move(i) instead + } + + q.enqueue_nowait(123456); + REQUIRE(q.overrun_counter() == 1); + + for (int i = 1; i < static_cast(q_size); i++) { + int item = -1; + q.dequeue(item); + REQUIRE(item == i); + } + + // last item pushed has overridden the oldest. + int item = -1; + q.dequeue(item); + REQUIRE(item == 123456); +} diff --git a/lib/spdlog/tests/test_pattern_formatter.cpp b/lib/spdlog/tests/test_pattern_formatter.cpp new file mode 100644 index 0000000..cd0df81 --- /dev/null +++ b/lib/spdlog/tests/test_pattern_formatter.cpp @@ -0,0 +1,667 @@ +#include "includes.h" +#include "test_sink.h" +#include +#include + +using spdlog::memory_buf_t; +using spdlog::details::to_string_view; + +// log to str and return it +template +static std::string log_to_str(const std::string &msg, const Args &...args) { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("pattern_tester", oss_sink); + oss_logger.set_level(spdlog::level::info); + + oss_logger.set_formatter( + std::unique_ptr(new spdlog::pattern_formatter(args...))); + + oss_logger.info(msg); + return oss.str(); +} + +// log to str and return it with time +template +static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time, + const std::string &msg, + const Args &...args) { + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + spdlog::logger oss_logger("pattern_tester", oss_sink); + oss_logger.set_level(spdlog::level::info); + + oss_logger.set_formatter( + std::unique_ptr(new spdlog::pattern_formatter(args...))); + + oss_logger.log(log_time, {}, spdlog::level::info, msg); + return oss.str(); +} + +TEST_CASE("custom eol", "[pattern_formatter]") { + std::string msg = "Hello custom eol test"; + std::string eol = ";)"; + REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol); +} + +TEST_CASE("empty format", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "").empty()); +} + +TEST_CASE("empty format2", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("level", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%l] %v", spdlog::pattern_time_type::local, "\n") == + "[info] Some message\n"); +} + +TEST_CASE("short level", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%L] %v", spdlog::pattern_time_type::local, "\n") == + "[I] Some message\n"); +} + +TEST_CASE("name", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); +} + +TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") { + auto now_tm = spdlog::details::os::localtime(); + std::stringstream oss; + oss << std::setfill('0') << std::setw(2) << now_tm.tm_mon + 1 << "/" << std::setw(2) + << now_tm.tm_mday << "/" << std::setw(2) << (now_tm.tm_year + 1900) % 1000 + << " Some message\n"; + REQUIRE(log_to_str("Some message", "%D %v", spdlog::pattern_time_type::local, "\n") == + oss.str()); +} + +// see test_timezone.cpp for actual UTC offset calculation tests +TEST_CASE("UTC offset", "[pattern_formatter]") { + using namespace std::chrono_literals; + const auto now = std::chrono::system_clock::now(); + std::string result = + log_to_str_with_time(now, "Some message", "%z", spdlog::pattern_time_type::local, "\n"); + +#ifndef SPDLOG_NO_TZ_OFFSET + // Match format: +HH:MM or -HH:MM + std::regex re(R"([+-]\d{2}:[0-5]\d\n)"); + REQUIRE(std::regex_match(result, re)); +#else + REQUIRE(result == "+??:??\n"); +#endif +} + +TEST_CASE("color range test1", "[pattern_formatter]") { + auto formatter = std::make_shared( + "%^%v%$", spdlog::pattern_time_type::local, "\n"); + + memory_buf_t buf; + spdlog::fmt_lib::format_to(std::back_inserter(buf), "Hello"); + memory_buf_t formatted; + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, + spdlog::string_view_t(buf.data(), buf.size())); + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("hello", "%^%v%$", spdlog::pattern_time_type::local, "\n") == "hello\n"); +} + +TEST_CASE("color range test2", "[pattern_formatter]") { + auto formatter = + std::make_shared("%^%$", spdlog::pattern_time_type::local, "\n"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, ""); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 0); + REQUIRE(log_to_str("", "%^%$", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("color range test3", "[pattern_formatter]") { + auto formatter = std::make_shared("%^***%$"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 3); +} + +TEST_CASE("color range test4", "[pattern_formatter]") { + auto formatter = std::make_shared( + "XX%^YYY%$", spdlog::pattern_time_type::local, "\n"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 5); + REQUIRE(log_to_str("ignored", "XX%^YYY%$", spdlog::pattern_time_type::local, "\n") == + "XXYYY\n"); +} + +TEST_CASE("color range test5", "[pattern_formatter]") { + auto formatter = std::make_shared("**%^"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 2); + REQUIRE(msg.color_range_end == 0); +} + +TEST_CASE("color range test6", "[pattern_formatter]") { + auto formatter = std::make_shared("**%$"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored"); + memory_buf_t formatted; + formatter->format(msg, formatted); + REQUIRE(msg.color_range_start == 0); + REQUIRE(msg.color_range_end == 2); +} + +// +// Test padding +// + +TEST_CASE("level_left_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%8l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info] Some message\n"); + REQUIRE(log_to_str("Some message", "[%8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info] Some message\n"); +} + +TEST_CASE("level_right_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-8l] %v", spdlog::pattern_time_type::local, "\n") == + "[info ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[info ] Some message\n"); +} + +TEST_CASE("level_center_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=8l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=8!l] %v", spdlog::pattern_time_type::local, "\n") == + "[ info ] Some message\n"); +} + +TEST_CASE("short level_left_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%3L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I] Some message\n"); + REQUIRE(log_to_str("Some message", "[%3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I] Some message\n"); +} + +TEST_CASE("short level_right_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-3L] %v", spdlog::pattern_time_type::local, "\n") == + "[I ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[I ] Some message\n"); +} + +TEST_CASE("short level_center_padded", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=3L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I ] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=3!L] %v", spdlog::pattern_time_type::local, "\n") == + "[ I ] Some message\n"); +} + +TEST_CASE("left_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("right_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%-3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("center_padded_short", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%=3n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester] Some message\n"); + REQUIRE(log_to_str("Some message", "[%=3!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pat] Some message\n"); +} + +TEST_CASE("left_padded_huge", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-300n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); + + REQUIRE(log_to_str("Some message", "[%-300!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); +} + +TEST_CASE("left_padded_max", "[pattern_formatter]") { + REQUIRE(log_to_str("Some message", "[%-64n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); + + REQUIRE(log_to_str("Some message", "[%-64!n] %v", spdlog::pattern_time_type::local, "\n") == + "[pattern_tester ] Some message\n"); +} + +// Test padding + truncate flag + +TEST_CASE("paddinng_truncate", "[pattern_formatter]") { + REQUIRE(log_to_str("123456", "%6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%7!v", spdlog::pattern_time_type::local, "\n") == " 123456\n"); + + REQUIRE(log_to_str("123456", "%-6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%-5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%-7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n"); + + REQUIRE(log_to_str("123456", "%=6!v", spdlog::pattern_time_type::local, "\n") == "123456\n"); + REQUIRE(log_to_str("123456", "%=5!v", spdlog::pattern_time_type::local, "\n") == "12345\n"); + REQUIRE(log_to_str("123456", "%=7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n"); + + REQUIRE(log_to_str("123456", "%0!v", spdlog::pattern_time_type::local, "\n") == "\n"); +} + +TEST_CASE("padding_truncate_funcname", "[pattern_formatter]") { + spdlog::sinks::test_sink_st test_sink; + + const char *pattern = "%v [%5!!]"; + auto formatter = std::unique_ptr(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, "function"}, "test_logger", + spdlog::level::info, "message"}; + test_sink.log(msg2); + REQUIRE(test_sink.lines()[1] == "message [funct]"); +} + +TEST_CASE("padding_funcname", "[pattern_formatter]") { + spdlog::sinks::test_sink_st test_sink; + + const char *pattern = "%v [%10!]"; + auto formatter = std::unique_ptr(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]") { + auto formatter_1 = std::make_shared(); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-default-formatter2", "[pattern_formatter]") { + auto formatter_1 = std::make_shared("%+"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-formatter", "[pattern_formatter]") { + auto formatter_1 = std::make_shared("%D %X [%] [%n] %v"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +TEST_CASE("clone-formatter-2", "[pattern_formatter]") { + using spdlog::pattern_time_type; + auto formatter_1 = std::make_shared( + "%D %X [%] [%n] %v", pattern_time_type::utc, "xxxxxx\n"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "test2"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2)); +} + +class custom_test_flag : public spdlog::custom_flag_formatter { +public: + explicit custom_test_flag(std::string txt) + : some_txt{std::move(txt)} {} + + void format(const spdlog::details::log_msg &, + const std::tm &tm, + spdlog::memory_buf_t &dest) override { + if (some_txt == "throw_me") { + throw spdlog::spdlog_ex("custom_flag_exception_test"); + } else if (some_txt == "time") { + auto formatted = spdlog::fmt_lib::format("{:d}:{:02d}{:s}", tm.tm_hour % 12, tm.tm_min, + tm.tm_hour / 12 ? "PM" : "AM"); + dest.append(formatted.data(), formatted.data() + formatted.size()); + return; + } + some_txt = std::string(padinfo_.width_, ' ') + some_txt; + dest.append(some_txt.data(), some_txt.data() + some_txt.size()); + } + spdlog::details::padding_info get_padding_info() { return padinfo_; } + + std::string some_txt; + + std::unique_ptr clone() const override { + return spdlog::details::make_unique(some_txt); + } +}; +// test clone with custom flag formatters +TEST_CASE("clone-custom_formatter", "[pattern_formatter]") { + auto formatter_1 = std::make_shared(); + formatter_1->add_flag('t', "custom_output").set_pattern("[%n] [%t] %v"); + auto formatter_2 = formatter_1->clone(); + std::string logger_name = "logger-name"; + spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message"); + + memory_buf_t formatted_1; + memory_buf_t formatted_2; + formatter_1->format(msg, formatted_1); + formatter_2->format(msg, formatted_2); + + auto expected = spdlog::fmt_lib::format("[logger-name] [custom_output] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_1) == expected); + REQUIRE(to_string_view(formatted_2) == expected); +} + +// +// Test source location formatting +// + +#ifdef _WIN32 +static const char *const test_path = "\\a\\b\\c/myfile.cpp"; +#else +static const char *const test_path = "/a/b//myfile.cpp"; +#endif + +TEST_CASE("short filename formatter-1", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{test_path, 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == "myfile.cpp"); +} + +TEST_CASE("short filename formatter-2", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s:%#", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{"myfile.cpp", 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == "myfile.cpp:123"); +} + +TEST_CASE("short filename formatter-3", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%s %v", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{"", 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == " Hello"); +} + +TEST_CASE("full filename formatter", "[pattern_formatter]") { + spdlog::pattern_formatter formatter("%g", spdlog::pattern_time_type::local, ""); + memory_buf_t formatted; + std::string logger_name = "logger-name"; + spdlog::source_loc source_loc{test_path, 123, "some_func()"}; + spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); + formatter.format(msg, formatted); + + REQUIRE(to_string_view(formatted) == test_path); +} + +TEST_CASE("custom flags", "[pattern_formatter]") { + auto formatter = std::make_shared(); + formatter->add_flag('t', "custom1") + .add_flag('u', "custom2") + .set_pattern("[%n] [%t] [%u] %v"); + + memory_buf_t formatted; + + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [custom2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); +} + +TEST_CASE("custom flags-padding", "[pattern_formatter]") { + auto formatter = std::make_shared(); + formatter->add_flag('t', "custom1") + .add_flag('u', "custom2") + .set_pattern("[%n] [%t] [%5u] %v"); + + memory_buf_t formatted; + + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [ custom2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); +} + +TEST_CASE("custom flags-exception", "[pattern_formatter]") { + auto formatter = std::make_shared(); + formatter->add_flag('t', "throw_me") + .add_flag('u', "custom2") + .set_pattern("[%n] [%t] [%u] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + CHECK_THROWS_AS(formatter->format(msg, formatted), spdlog::spdlog_ex); +} + +TEST_CASE("override need_localtime", "[pattern_formatter]") { + auto formatter = + std::make_shared(spdlog::pattern_time_type::local, "\n"); + formatter->add_flag('t', "time").set_pattern("%t> %v"); + + { + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + REQUIRE(to_string_view(formatted) == "0:00AM> some message\n"); + } + + { + formatter->need_localtime(); + + auto now_tm = spdlog::details::os::localtime(); + std::stringstream oss; + oss << (now_tm.tm_hour % 12) << ":" << std::setfill('0') << std::setw(2) << now_tm.tm_min + << (now_tm.tm_hour / 12 ? "PM" : "AM") << "> some message\n"; + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + REQUIRE(to_string_view(formatted) == oss.str()); + } +} + +#ifndef SPDLOG_NO_TLS +TEST_CASE("mdc formatter test-1", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc formatter value update", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted_1; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted_1); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_1) == expected); + + spdlog::mdc::put("mdc_key_1", "new_mdc_value_1"); + memory_buf_t formatted_2; + formatter->format(msg, formatted_2); + expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:new_mdc_value_1 mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_2) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc different threads", "[pattern_formatter]") { + auto formatter = std::make_shared(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + + memory_buf_t formatted_2; + + auto lambda_1 = [formatter, msg]() { + spdlog::mdc::put("mdc_key", "thread_1_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_1_id] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + auto lambda_2 = [formatter, msg]() { + spdlog::mdc::put("mdc_key", "thread_2_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_2_id] some message{}", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + std::thread thread_1(lambda_1); + std::thread thread_2(lambda_2); + + thread_1.join(); + thread_2.join(); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc remove key", "[pattern_formatter]") { + spdlog::mdc::put("mdc_key_1", "mdc_value_1"); + spdlog::mdc::put("mdc_key_2", "mdc_value_2"); + spdlog::mdc::remove("mdc_key_1"); + + auto formatter = std::make_shared(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key_2:mdc_value_2] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} + +TEST_CASE("mdc empty", "[pattern_formatter]") { + auto formatter = std::make_shared(); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format("[logger-name] [info] [] some message{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +} +#endif diff --git a/lib/spdlog/tests/test_registry.cpp b/lib/spdlog/tests/test_registry.cpp new file mode 100644 index 0000000..1805ae7 --- /dev/null +++ b/lib/spdlog/tests/test_registry.cpp @@ -0,0 +1,125 @@ +#include "includes.h" + +static const char *const tested_logger_name = "null_logger"; +static const char *const tested_logger_name2 = "null_logger2"; + +#ifndef SPDLOG_NO_EXCEPTIONS +TEST_CASE("register_drop", "[registry]") { + spdlog::drop_all(); + spdlog::create(tested_logger_name); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registering existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), + spdlog::spdlog_ex); +} + +TEST_CASE("explicit register", "[registry]") { + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, + std::make_shared()); + spdlog::register_logger(logger); + REQUIRE(spdlog::get(tested_logger_name) != nullptr); + // Throw if registering existing name + REQUIRE_THROWS_AS(spdlog::create(tested_logger_name), + spdlog::spdlog_ex); +} +#endif + +TEST_CASE("register_or_replace", "[registry]") { + spdlog::drop_all(); + auto logger1 = std::make_shared( + tested_logger_name, std::make_shared()); + spdlog::register_logger(logger1); + REQUIRE(spdlog::get(tested_logger_name) == logger1); + + auto logger2 = std::make_shared( + tested_logger_name, std::make_shared()); + spdlog::register_or_replace(logger2); + REQUIRE(spdlog::get(tested_logger_name) == logger2); +} + +TEST_CASE("apply_all", "[registry]") { + spdlog::drop_all(); + auto logger = std::make_shared(tested_logger_name, + std::make_shared()); + spdlog::register_logger(logger); + auto logger2 = std::make_shared( + tested_logger_name2, std::make_shared()); + spdlog::register_logger(logger2); + + int counter = 0; + spdlog::apply_all([&counter](std::shared_ptr) { counter++; }); + REQUIRE(counter == 2); + + counter = 0; + spdlog::drop(tested_logger_name2); + spdlog::apply_all([&counter](std::shared_ptr l) { + REQUIRE(l->name() == tested_logger_name); + counter++; + }); + REQUIRE(counter == 1); +} + +TEST_CASE("drop", "[registry]") { + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop-default", "[registry]") { + spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name)); + spdlog::drop(tested_logger_name); + REQUIRE_FALSE(spdlog::default_logger()); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); +} + +TEST_CASE("drop_all", "[registry]") { + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::create(tested_logger_name2); + spdlog::drop_all(); + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name2)); + REQUIRE_FALSE(spdlog::default_logger()); +} + +TEST_CASE("drop non existing", "[registry]") { + spdlog::drop_all(); + spdlog::create(tested_logger_name); + spdlog::drop("some_name"); + REQUIRE_FALSE(spdlog::get("some_name")); + REQUIRE(spdlog::get(tested_logger_name)); + spdlog::drop_all(); +} + +TEST_CASE("default logger", "[registry]") { + spdlog::drop_all(); + spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name)); + REQUIRE(spdlog::get(tested_logger_name) == spdlog::default_logger()); + spdlog::drop_all(); +} + +TEST_CASE("set_default_logger(nullptr)", "[registry]") { + spdlog::set_default_logger(nullptr); + REQUIRE_FALSE(spdlog::default_logger()); +} + +TEST_CASE("disable automatic registration", "[registry]") { + // set some global parameters + spdlog::level::level_enum log_level = spdlog::level::level_enum::warn; + spdlog::set_level(log_level); + // but disable automatic registration + spdlog::set_automatic_registration(false); + auto logger1 = spdlog::create( + tested_logger_name, SPDLOG_FILENAME_T("filename"), 11, 59); + auto logger2 = spdlog::create_async(tested_logger_name2); + // loggers should not be part of the registry + REQUIRE_FALSE(spdlog::get(tested_logger_name)); + REQUIRE_FALSE(spdlog::get(tested_logger_name2)); + // but make sure they are still initialized according to global defaults + REQUIRE(logger1->level() == log_level); + REQUIRE(logger2->level() == log_level); + spdlog::set_level(spdlog::level::info); + spdlog::set_automatic_registration(true); +} diff --git a/lib/spdlog/tests/test_ringbuffer.cpp b/lib/spdlog/tests/test_ringbuffer.cpp new file mode 100644 index 0000000..81d7916 --- /dev/null +++ b/lib/spdlog/tests/test_ringbuffer.cpp @@ -0,0 +1,52 @@ +#include "includes.h" +#include "spdlog/sinks/ringbuffer_sink.h" + +TEST_CASE("ringbuffer invalid size", "[ringbuffer]") { + REQUIRE_THROWS_AS(spdlog::sinks::ringbuffer_sink_mt(0), spdlog::spdlog_ex); +} + +TEST_CASE("ringbuffer stores formatted messages", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(3); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg1"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg2"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg3"}); + + auto formatted = sink.last_formatted(); + REQUIRE(formatted.size() == 3); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("msg1{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("msg2{}", default_eol)); + REQUIRE(formatted[2] == spdlog::fmt_lib::format("msg3{}", default_eol)); +} + +TEST_CASE("ringbuffer overrun keeps last items", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(2); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "first"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "second"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "third"}); + + auto formatted = sink.last_formatted(); + REQUIRE(formatted.size() == 2); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("second{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("third{}", default_eol)); +} + +TEST_CASE("ringbuffer retrieval limit", "[ringbuffer]") { + spdlog::sinks::ringbuffer_sink_st sink(3); + sink.set_pattern("%v"); + + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "A"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "B"}); + sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "C"}); + + auto formatted = sink.last_formatted(2); + REQUIRE(formatted.size() == 2); + using spdlog::details::os::default_eol; + REQUIRE(formatted[0] == spdlog::fmt_lib::format("B{}", default_eol)); + REQUIRE(formatted[1] == spdlog::fmt_lib::format("C{}", default_eol)); +} diff --git a/lib/spdlog/tests/test_sink.h b/lib/spdlog/tests/test_sink.h new file mode 100644 index 0000000..9c09452 --- /dev/null +++ b/lib/spdlog/tests/test_sink.h @@ -0,0 +1,70 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" +#include "spdlog/fmt/fmt.h" +#include +#include +#include + +namespace spdlog { +namespace sinks { + +template +class test_sink : public base_sink { + const size_t lines_to_save = 100; + +public: + size_t msg_counter() { + std::lock_guard lock(base_sink::mutex_); + return msg_counter_; + } + + size_t flush_counter() { + std::lock_guard lock(base_sink::mutex_); + return flush_counter_; + } + + void set_delay(std::chrono::milliseconds delay) { + std::lock_guard lock(base_sink::mutex_); + delay_ = delay; + } + + // return last output without the eol + std::vector lines() { + std::lock_guard lock(base_sink::mutex_); + return lines_; + } + +protected: + void sink_it_(const details::log_msg &msg) override { + memory_buf_t formatted; + base_sink::formatter_->format(msg, formatted); + // save the line without the eol + auto eol_len = strlen(details::os::default_eol); + using diff_t = typename std::iterator_traits::difference_type; + if (lines_.size() < lines_to_save) { + lines_.emplace_back(formatted.begin(), formatted.end() - static_cast(eol_len)); + } + msg_counter_++; + std::this_thread::sleep_for(delay_); + } + + void flush_() override { flush_counter_++; } + + size_t msg_counter_{0}; + size_t flush_counter_{0}; + std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()}; + std::vector lines_; +}; + +using test_sink_mt = test_sink; +using test_sink_st = test_sink; + +} // namespace sinks +} // namespace spdlog diff --git a/lib/spdlog/tests/test_stdout_api.cpp b/lib/spdlog/tests/test_stdout_api.cpp new file mode 100644 index 0000000..54cd47a --- /dev/null +++ b/lib/spdlog/tests/test_stdout_api.cpp @@ -0,0 +1,96 @@ +/* + * This content is released under the MIT License as specified in + * https://raw.githubusercontent.com/gabime/spdlog/master/LICENSE + */ +#include "includes.h" +#include "spdlog/sinks/stdout_sinks.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +TEST_CASE("stdout_st", "[stdout]") { + auto l = spdlog::stdout_logger_st("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace("Test stdout_st"); + spdlog::drop_all(); +} + +TEST_CASE("stdout_mt", "[stdout]") { + auto l = spdlog::stdout_logger_mt("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::debug); + l->debug("Test stdout_mt"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_st", "[stderr]") { + auto l = spdlog::stderr_logger_st("test"); + l->set_pattern("%+"); + l->info("Test stderr_st"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_mt", "[stderr]") { + auto l = spdlog::stderr_logger_mt("test"); + l->set_pattern("%+"); + l->info("Test stderr_mt"); + l->warn("Test stderr_mt"); + l->error("Test stderr_mt"); + l->critical("Test stderr_mt"); + spdlog::drop_all(); +} + +// color loggers +TEST_CASE("stdout_color_st", "[stdout]") { + auto l = spdlog::stdout_color_st("test"); + l->set_pattern("%+"); + l->info("Test stdout_color_st"); + spdlog::drop_all(); +} + +TEST_CASE("stdout_color_mt", "[stdout]") { + auto l = spdlog::stdout_color_mt("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace("Test stdout_color_mt"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_color_st", "[stderr]") { + auto l = spdlog::stderr_color_st("test"); + l->set_pattern("%+"); + l->set_level(spdlog::level::debug); + l->debug("Test stderr_color_st"); + spdlog::drop_all(); +} + +TEST_CASE("stderr_color_mt", "[stderr]") { + auto l = spdlog::stderr_color_mt("test"); + l->set_pattern("%+"); + l->info("Test stderr_color_mt"); + l->warn("Test stderr_color_mt"); + l->error("Test stderr_color_mt"); + l->critical("Test stderr_color_mt"); + spdlog::drop_all(); +} + +TEST_CASE("show_utc_offset", "[stdout]") { + auto l = spdlog::stdout_color_mt("test"); + l->set_pattern("[%c %z] [%n] [%^%l%$] %v"); + l->info("Full date"); + spdlog::drop_all(); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +TEST_CASE("wchar_api", "[stdout]") { + auto l = spdlog::stdout_logger_st("wchar_logger"); + l->set_pattern("%+"); + l->set_level(spdlog::level::trace); + l->trace(L"Test wchar_api"); + l->trace(L"Test wchar_api {}", L"param"); + l->trace(L"Test wchar_api {}", 1); + l->trace(L"Test wchar_api {}", std::wstring{L"wstring param"}); + l->trace(std::wstring{L"Test wchar_api wstring"}); + SPDLOG_LOGGER_DEBUG(l, L"Test SPDLOG_LOGGER_DEBUG {}", L"param"); + spdlog::drop_all(); +} +#endif diff --git a/lib/spdlog/tests/test_stopwatch.cpp b/lib/spdlog/tests/test_stopwatch.cpp new file mode 100644 index 0000000..b1b4b19 --- /dev/null +++ b/lib/spdlog/tests/test_stopwatch.cpp @@ -0,0 +1,42 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/stopwatch.h" + +TEST_CASE("stopwatch1", "[stopwatch]") { + using std::chrono::milliseconds; + using clock = std::chrono::steady_clock; + milliseconds wait_ms(500); + milliseconds tolerance_ms(250); + auto start = clock::now(); + spdlog::stopwatch sw; + std::this_thread::sleep_for(wait_ms); + auto stop = clock::now(); + auto diff_ms = std::chrono::duration_cast(stop - start); + REQUIRE(sw.elapsed() >= diff_ms); + REQUIRE(sw.elapsed() <= diff_ms + tolerance_ms); +} + +TEST_CASE("stopwatch2", "[stopwatch]") { + using spdlog::sinks::test_sink_st; + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using clock = std::chrono::steady_clock; + + clock::duration wait_duration(milliseconds(500)); + clock::duration tolerance_duration(milliseconds(250)); + + auto test_sink = std::make_shared(); + + auto start = clock::now(); + spdlog::stopwatch sw; + spdlog::logger logger("test-stopwatch", test_sink); + logger.set_pattern("%v"); + std::this_thread::sleep_for(wait_duration); + auto stop = clock::now(); + logger.info("{}", sw); + auto val = std::stod(test_sink->lines()[0]); + auto diff_duration = duration_cast>(stop - start); + + REQUIRE(val >= (diff_duration).count() - 0.001); + REQUIRE(val <= (diff_duration + tolerance_duration).count()); +} diff --git a/lib/spdlog/tests/test_systemd.cpp b/lib/spdlog/tests/test_systemd.cpp new file mode 100644 index 0000000..e363657 --- /dev/null +++ b/lib/spdlog/tests/test_systemd.cpp @@ -0,0 +1,14 @@ +#include "includes.h" +#include "spdlog/sinks/systemd_sink.h" + +TEST_CASE("systemd", "[all]") { + auto systemd_sink = std::make_shared(); + spdlog::logger logger("spdlog_systemd_test", systemd_sink); + logger.set_level(spdlog::level::trace); + logger.trace("test spdlog trace"); + logger.debug("test spdlog debug"); + SPDLOG_LOGGER_INFO((&logger), "test spdlog info"); + SPDLOG_LOGGER_WARN((&logger), "test spdlog warn"); + SPDLOG_LOGGER_ERROR((&logger), "test spdlog error"); + SPDLOG_LOGGER_CRITICAL((&logger), "test spdlog critical"); +} diff --git a/lib/spdlog/tests/test_time_point.cpp b/lib/spdlog/tests/test_time_point.cpp new file mode 100644 index 0000000..b7b1b23 --- /dev/null +++ b/lib/spdlog/tests/test_time_point.cpp @@ -0,0 +1,35 @@ +#include "includes.h" +#include "test_sink.h" +#include "spdlog/async.h" + +TEST_CASE("time_point1", "[time_point log_msg]") { + std::shared_ptr test_sink(new spdlog::sinks::test_sink_st); + spdlog::logger logger("test-time_point", test_sink); + + spdlog::source_loc source{}; + std::chrono::system_clock::time_point tp{std::chrono::system_clock::now()}; + test_sink->set_pattern("%T.%F"); // interested in the time_point + + // all the following should have the same time + test_sink->set_delay(std::chrono::milliseconds(10)); + for (int i = 0; i < 5; i++) { + spdlog::details::log_msg msg{tp, source, "test_logger", spdlog::level::info, "message"}; + test_sink->log(msg); + } + + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(tp, source, spdlog::level::info, "formatted message"); + logger.log(source, spdlog::level::info, + "formatted message"); // last line has different time_point + + // now the real test... that the times are the same. + std::vector lines = test_sink->lines(); + REQUIRE(lines[0] == lines[1]); + REQUIRE(lines[2] == lines[3]); + REQUIRE(lines[4] == lines[5]); + REQUIRE(lines[6] == lines[7]); + REQUIRE(lines[8] != lines[9]); + spdlog::drop_all(); +} diff --git a/lib/spdlog/tests/test_timezone.cpp b/lib/spdlog/tests/test_timezone.cpp new file mode 100644 index 0000000..7bee9c5 --- /dev/null +++ b/lib/spdlog/tests/test_timezone.cpp @@ -0,0 +1,146 @@ +#ifndef SPDLOG_NO_TZ_OFFSET + +#include "includes.h" +#include +#include +#include + +// Helper to construct a simple std::tm from components +std::tm make_tm(int year, int month, int day, int hour, int minute) { + std::tm t; + std::memset(&t, 0, sizeof(t)); + t.tm_year = year - 1900; + t.tm_mon = month - 1; + t.tm_mday = day; + t.tm_hour = hour; + t.tm_min = minute; + t.tm_sec = 0; + t.tm_isdst = -1; + std::mktime(&t); + return t; +} + +// Cross-platform RAII Helper to safely set/restore process timezone +class ScopedTZ { + std::string original_tz_; + bool has_original_ = false; + +public: + explicit ScopedTZ(const std::string& tz_name) { + // save current TZ +#ifdef _WIN32 + char* buf = nullptr; + size_t len = 0; + if (_dupenv_s(&buf, &len, "TZ") == 0 && buf != nullptr) { + original_tz_ = std::string(buf); + has_original_ = true; + free(buf); + } +#else + const char* tz = std::getenv("TZ"); + if (tz) { + original_tz_ = tz; + has_original_ = true; + } +#endif + + // set new TZ +#ifdef _WIN32 + _putenv_s("TZ", tz_name.c_str()); + _tzset(); +#else + setenv("TZ", tz_name.c_str(), 1); + tzset(); +#endif + } + + ~ScopedTZ() { + // restore original TZ +#ifdef _WIN32 + if (has_original_) { + _putenv_s("TZ", original_tz_.c_str()); + } else { + _putenv_s("TZ", ""); + } + _tzset(); +#else + if (has_original_) { + setenv("TZ", original_tz_.c_str(), 1); + } else { + unsetenv("TZ"); + } + tzset(); +#endif + } +}; + +using spdlog::details::os::utc_minutes_offset; + +TEST_CASE("UTC Offset - Western Hemisphere (USA - Standard Time)", "[timezone][west]") { + // EST5EDT: Eastern Standard Time (UTC-5) + ScopedTZ tz("EST5EDT"); + + // Jan 15th (Winter) + auto tm = make_tm(2023, 1, 15, 12, 0); + REQUIRE(utc_minutes_offset(tm) == -300); +} + +TEST_CASE("UTC Offset - Eastern Hemisphere (Europe/Israel - Standard Time)", "[timezone][east]") { + // IST-2IDT: Israel Standard Time (UTC+2) + ScopedTZ tz("IST-2IDT"); + + // Jan 15th (Winter) + auto tm = make_tm(2023, 1, 15, 12, 0); + REQUIRE(utc_minutes_offset(tm) == 120); +} + +TEST_CASE("UTC Offset - Zero Offset (UTC/GMT)", "[timezone][utc]") { + ScopedTZ tz("GMT0"); + + // Check Winter + auto tm_winter = make_tm(2023, 1, 15, 12, 0); + REQUIRE(utc_minutes_offset(tm_winter) == 0); + + // Check Summer (GMT never shifts, so this should also be 0) + auto tm_summer = make_tm(2023, 7, 15, 12, 0); + REQUIRE(utc_minutes_offset(tm_summer) == 0); +} + +TEST_CASE("UTC Offset - Non-Integer Hour Offsets (India)", "[timezone][partial]") { + // IST-5:30: India Standard Time (UTC+5:30) + ScopedTZ tz("IST-5:30"); + + auto tm = make_tm(2023, 1, 15, 12, 0); + REQUIRE(utc_minutes_offset(tm) == 330); +} + +TEST_CASE("UTC Offset - Edge Case: Negative Offset Crossing Midnight", "[timezone][edge]") { + ScopedTZ tz("EST5EDT"); + // Late night Dec 31st, 2023 + auto tm = make_tm(2023, 12, 31, 23, 59); + REQUIRE(utc_minutes_offset(tm) == -300); +} + +TEST_CASE("UTC Offset - Edge Case: Leap Year", "[timezone][edge]") { + ScopedTZ tz("EST5EDT"); + // Feb 29, 2024 (Leap Day) - Winter + auto tm = make_tm(2024, 2, 29, 12, 0); + REQUIRE(utc_minutes_offset(tm) == -300); +} + +TEST_CASE("UTC Offset - Edge Case: Invalid Date (Pre-Epoch)", "[timezone][edge]") { +#ifdef _WIN32 + // Windows mktime returns -1 for dates before 1970. + // We expect the function to safely return 0 (fallback). + auto tm = make_tm(1960, 1, 1, 12, 0); + REQUIRE(utc_minutes_offset(tm) == 0); +#else + // Unix mktime handles pre-1970 dates correctly. + // We expect the actual historical offset (EST was UTC-5 in 1960). + ScopedTZ tz("EST5EDT"); + auto tm = make_tm(1960, 1, 1, 12, 0); + REQUIRE(utc_minutes_offset(tm) == -300); +#endif +} + +#endif // !SPDLOG_NO_TZ_OFFSET \ No newline at end of file diff --git a/lib/spdlog/tests/utils.cpp b/lib/spdlog/tests/utils.cpp new file mode 100644 index 0000000..51a9b25 --- /dev/null +++ b/lib/spdlog/tests/utils.cpp @@ -0,0 +1,102 @@ +#include "includes.h" + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +void prepare_logdir() { + spdlog::drop_all(); +#ifdef _WIN32 + system("rmdir /S /Q test_logs"); +#else + auto rv = system("rm -rf test_logs"); + if (rv != 0) { + throw std::runtime_error("Failed to rm -rf test_logs"); + } +#endif +} + +std::string file_contents(const std::string &filename) { + std::ifstream ifs(filename, std::ios_base::binary); + if (!ifs) { + throw std::runtime_error("Failed open file "); + } + return std::string((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); +} + +std::size_t count_lines(const std::string &filename) { + std::ifstream ifs(filename); + if (!ifs) { + throw std::runtime_error("Failed open file "); + } + + std::string line; + size_t counter = 0; + while (std::getline(ifs, line)) counter++; + return counter; +} + +void require_message_count(const std::string &filename, const std::size_t messages) { + if (strlen(spdlog::details::os::default_eol) == 0) { + REQUIRE(count_lines(filename) == 1); + } else { + REQUIRE(count_lines(filename) == messages); + } +} + +std::size_t get_filesize(const std::string &filename) { + std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); + if (!ifs) { + throw std::runtime_error("Failed open file " + filename); + } + return static_cast(ifs.tellg()); +} + +// source: https://stackoverflow.com/a/2072890/192001 +bool ends_with(std::string const &value, std::string const &ending) { + if (ending.size() > value.size()) { + return false; + } + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +#ifdef _WIN32 +// Based on: https://stackoverflow.com/a/37416569/192001 +std::size_t count_files(const std::string &folder) { + size_t counter = 0; + WIN32_FIND_DATAA ffd; + + // Start iterating over the files in the folder directory. + HANDLE hFind = ::FindFirstFileA((folder + "\\*").c_str(), &ffd); + if (hFind != INVALID_HANDLE_VALUE) { + do // Managed to locate and create an handle to that folder. + { + if (ffd.cFileName[0] != '.') counter++; + } while (::FindNextFileA(hFind, &ffd) != 0); + ::FindClose(hFind); + } else { + throw std::runtime_error("Failed open folder " + folder); + } + + return counter; +} +#else +// Based on: https://stackoverflow.com/a/2802255/192001 +std::size_t count_files(const std::string &folder) { + size_t counter = 0; + DIR *dp = opendir(folder.c_str()); + if (dp == nullptr) { + throw std::runtime_error("Failed open folder " + folder); + } + + struct dirent *ep = nullptr; + while ((ep = readdir(dp)) != nullptr) { + if (ep->d_name[0] != '.') counter++; + } + (void)closedir(dp); + return counter; +} +#endif diff --git a/lib/spdlog/tests/utils.h b/lib/spdlog/tests/utils.h new file mode 100644 index 0000000..53c09b4 --- /dev/null +++ b/lib/spdlog/tests/utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +std::size_t count_files(const std::string &folder); + +void prepare_logdir(); + +std::string file_contents(const std::string &filename); + +std::size_t count_lines(const std::string &filename); + +void require_message_count(const std::string &filename, const std::size_t messages); + +std::size_t get_filesize(const std::string &filename); + +bool ends_with(std::string const &value, std::string const &ending); \ No newline at end of file