| @@ -0,0 +1,4 @@ | |||
| _build/ | |||
| __pycache__/ | |||
| .peru/ | |||
| .vscode/ | |||
| @@ -0,0 +1,136 @@ | |||
| #!/usr/bin/env python3 | |||
| import argparse | |||
| from pathlib import Path | |||
| import multiprocessing | |||
| import itertools | |||
| from concurrent.futures import ThreadPoolExecutor | |||
| from typing import Sequence, Iterable, Dict, Tuple | |||
| import subprocess | |||
| import time | |||
| import sys | |||
| HERE_DIR = Path(__file__).parent.absolute() | |||
| INCLUDE_DIRS = [ | |||
| 'external/taywee-args/include', | |||
| 'external/spdlog/include', | |||
| ] | |||
| def _compile_src(cxx: Path, cpp_file: Path) -> Tuple[Path, Path]: | |||
| build_dir = HERE_DIR / '_build' | |||
| src_dir = HERE_DIR / 'src' | |||
| relpath = cpp_file.relative_to(src_dir) | |||
| obj_path = build_dir / relpath.with_name(relpath.name + '.o') | |||
| obj_path.parent.mkdir(exist_ok=True, parents=True) | |||
| cmd = [ | |||
| cxx, | |||
| '-std=c++17', | |||
| '-static', | |||
| '-Wall', | |||
| '-Wextra', | |||
| '-Werror', | |||
| '-Wshadow', | |||
| '-Wconversion', | |||
| '-fdiagnostics-color', | |||
| '-g', | |||
| '-c', | |||
| '-O0', | |||
| f'-I{src_dir}', | |||
| cpp_file, | |||
| f'-o{obj_path}', | |||
| ] | |||
| cmd.extend( | |||
| itertools.chain.from_iterable( | |||
| ('-isystem', HERE_DIR / subdir) for subdir in INCLUDE_DIRS)) | |||
| msg = f'Compile C++ file: {cpp_file}' | |||
| print(msg) | |||
| start = time.time() | |||
| res = subprocess.run( | |||
| cmd, | |||
| stdout=subprocess.PIPE, | |||
| stderr=subprocess.STDOUT, | |||
| ) | |||
| if res.returncode != 0: | |||
| raise RuntimeError( | |||
| f'Compile command ({cmd}) failed for {cpp_file}:\n{res.stdout.decode()}' | |||
| ) | |||
| end = time.time() | |||
| print(f'{msg} - Done: {end - start:.2}s') | |||
| return cpp_file, obj_path | |||
| def compile_sources(cxx: Path, sources: Iterable[Path]) -> Dict[Path, Path]: | |||
| pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||
| return { | |||
| src: obj | |||
| for src, obj in pool.map(lambda s: _compile_src(cxx, s), sources) | |||
| } | |||
| def make_library(objects: Iterable[Path]) -> Path: | |||
| lib_file = HERE_DIR / '_build/libddslim.a' | |||
| cmd = ['ar', 'rsc', lib_file] | |||
| cmd.extend(objects) | |||
| if lib_file.exists(): | |||
| lib_file.unlink() | |||
| print(f'Creating static library {lib_file}') | |||
| subprocess.check_call(cmd) | |||
| return lib_file | |||
| def link_exe(cxx: Path, obj: Path, lib: Path, *, out: Path = None) -> Path: | |||
| if out is None: | |||
| basename = obj.stem | |||
| out = HERE_DIR / '_build/test' / (basename + '.exe') | |||
| out.parent.mkdir(exist_ok=True, parents=True) | |||
| print(f'Linking executable {out}') | |||
| subprocess.check_call([cxx, '-static', obj, lib, '-lstdc++fs', f'-o{out}']) | |||
| return out | |||
| def run_test(exe: Path) -> None: | |||
| print(f'Running test: {exe}') | |||
| subprocess.check_call([exe]) | |||
| def main(argv: Sequence[str]) -> int: | |||
| parser = argparse.ArgumentParser() | |||
| parser.add_argument( | |||
| '--test', action='store_true', help='Build and run tests') | |||
| parser.add_argument( | |||
| '--cxx', help='Path/name of the C++ compiler to use.', required=True) | |||
| args = parser.parse_args(argv) | |||
| all_sources = set(HERE_DIR.glob('src/**/*.cpp')) | |||
| test_sources = set(HERE_DIR.glob('src/**/*.test.cpp')) | |||
| main_sources = set(HERE_DIR.glob('src/**/*.main.cpp')) | |||
| lib_sources = (all_sources - test_sources) - main_sources | |||
| objects = compile_sources(Path(args.cxx), all_sources) | |||
| lib = make_library(objects[p] for p in lib_sources) | |||
| test_objs = (objects[p] for p in test_sources) | |||
| pool = ThreadPoolExecutor(multiprocessing.cpu_count() + 2) | |||
| test_exes = list( | |||
| pool.map(lambda o: link_exe(Path(args.cxx), o, lib), test_objs)) | |||
| main_exe = link_exe( | |||
| Path(args.cxx), | |||
| objects[next(iter(main_sources))], | |||
| lib, | |||
| out=HERE_DIR / '_build/ddslim') | |||
| if args.test: | |||
| list(pool.map(run_test, test_exes)) | |||
| print(f'Main executable generated at {main_exe}') | |||
| return 0 | |||
| if __name__ == "__main__": | |||
| sys.exit(main(sys.argv[1:])) | |||
| @@ -0,0 +1,93 @@ | |||
| // 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 withe 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 "spdlog/async_logger.h" | |||
| #include "spdlog/details/registry.h" | |||
| #include "spdlog/details/thread_pool.h" | |||
| #include <memory> | |||
| #include <mutex> | |||
| #include <functional> | |||
| 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<async_overflow_policy OverflowPolicy = async_overflow_policy::block> | |||
| struct async_factory_impl | |||
| { | |||
| template<typename Sink, typename... SinkArgs> | |||
| static std::shared_ptr<async_logger> 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<std::recursive_mutex> tp_lock(mutex); | |||
| auto tp = registry_inst.get_tp(); | |||
| if (tp == nullptr) | |||
| { | |||
| tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1); | |||
| registry_inst.set_tp(tp); | |||
| } | |||
| auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||
| auto new_logger = std::make_shared<async_logger>(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<async_overflow_policy::block>; | |||
| using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; | |||
| template<typename Sink, typename... SinkArgs> | |||
| inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args) | |||
| { | |||
| return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||
| } | |||
| template<typename Sink, typename... SinkArgs> | |||
| inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args) | |||
| { | |||
| return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||
| } | |||
| // set global thread pool. | |||
| inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start) | |||
| { | |||
| auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start); | |||
| details::registry::instance().set_tp(std::move(tp)); | |||
| } | |||
| // set global thread pool. | |||
| 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<spdlog::details::thread_pool> thread_pool() | |||
| { | |||
| return details::registry::instance().get_tp(); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,92 @@ | |||
| // 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 "spdlog/async_logger.h" | |||
| #endif | |||
| #include "spdlog/sinks/sink.h" | |||
| #include "spdlog/details/thread_pool.h" | |||
| #include <memory> | |||
| #include <string> | |||
| SPDLOG_INLINE spdlog::async_logger::async_logger( | |||
| std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> 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<details::thread_pool> 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) | |||
| { | |||
| if (auto pool_ptr = thread_pool_.lock()) | |||
| { | |||
| pool_ptr->post_log(shared_from_this(), msg, overflow_policy_); | |||
| } | |||
| else | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("async log: thread pool doesn't exist anymore")); | |||
| } | |||
| } | |||
| // send flush request to the thread pool | |||
| SPDLOG_INLINE void spdlog::async_logger::flush_() | |||
| { | |||
| if (auto pool_ptr = thread_pool_.lock()) | |||
| { | |||
| pool_ptr->post_flush(shared_from_this(), overflow_policy_); | |||
| } | |||
| else | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("async flush: thread pool doesn't exist anymore")); | |||
| } | |||
| } | |||
| // | |||
| // 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 &msg) | |||
| { | |||
| for (auto &sink : sinks_) | |||
| { | |||
| if (sink->should_log(msg.level)) | |||
| { | |||
| SPDLOG_TRY | |||
| { | |||
| sink->log(msg); | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| } | |||
| if (should_flush_(msg)) | |||
| { | |||
| backend_flush_(); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void spdlog::async_logger::backend_flush_() | |||
| { | |||
| for (auto &sink : sinks_) | |||
| { | |||
| SPDLOG_TRY | |||
| { | |||
| sink->flush(); | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| } | |||
| SPDLOG_INLINE std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) | |||
| { | |||
| auto cloned = std::make_shared<spdlog::async_logger>(*this); | |||
| cloned->name_ = std::move(new_name); | |||
| return cloned; | |||
| } | |||
| @@ -0,0 +1,68 @@ | |||
| // 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 "spdlog/logger.h" | |||
| 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. | |||
| }; | |||
| namespace details { | |||
| class thread_pool; | |||
| } | |||
| class async_logger final : public std::enable_shared_from_this<async_logger>, public logger | |||
| { | |||
| friend class details::thread_pool; | |||
| public: | |||
| template<typename It> | |||
| async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> 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<details::thread_pool> tp, | |||
| async_overflow_policy overflow_policy = async_overflow_policy::block); | |||
| async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, | |||
| async_overflow_policy overflow_policy = async_overflow_policy::block); | |||
| std::shared_ptr<logger> 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<details::thread_pool> thread_pool_; | |||
| async_overflow_policy overflow_policy_; | |||
| }; | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "async_logger-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,57 @@ | |||
| // 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 "spdlog/common.h" | |||
| #endif | |||
| namespace spdlog { | |||
| namespace level { | |||
| static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES; | |||
| static const char *short_level_names[] SPDLOG_SHORT_LEVEL_NAMES; | |||
| SPDLOG_INLINE string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT | |||
| { | |||
| 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 | |||
| { | |||
| int level = 0; | |||
| for (const auto &level_str : level_string_views) | |||
| { | |||
| if (level_str == name) | |||
| { | |||
| return static_cast<level::level_enum>(level); | |||
| } | |||
| level++; | |||
| } | |||
| 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) | |||
| { | |||
| memory_buf_t outbuf; | |||
| fmt::format_system_error(outbuf, last_errno, msg); | |||
| msg_ = fmt::to_string(outbuf); | |||
| } | |||
| SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT | |||
| { | |||
| return msg_.c_str(); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,245 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/tweakme.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include <atomic> | |||
| #include <chrono> | |||
| #include <initializer_list> | |||
| #include <memory> | |||
| #include <exception> | |||
| #include <string> | |||
| #include <type_traits> | |||
| #include <functional> | |||
| #ifdef _WIN32 | |||
| #ifndef NOMINMAX | |||
| #define NOMINMAX // prevent windows redefining min/max | |||
| #endif | |||
| #ifndef WIN32_LEAN_AND_MEAN | |||
| #define WIN32_LEAN_AND_MEAN | |||
| #endif | |||
| #include <windows.h> | |||
| #endif //_WIN32 | |||
| #ifdef SPDLOG_COMPILED_LIB | |||
| #undef SPDLOG_HEADER_ONLY | |||
| #define SPDLOG_INLINE | |||
| #else | |||
| #define SPDLOG_HEADER_ONLY | |||
| #define SPDLOG_INLINE inline | |||
| #endif | |||
| #include "spdlog/fmt/fmt.h" | |||
| // visual studio upto 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 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 __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_ALL() | |||
| #else | |||
| #define SPDLOG_TRY try | |||
| #define SPDLOG_THROW(ex) throw(ex) | |||
| #define SPDLOG_CATCH_ALL() catch (...) | |||
| #endif | |||
| namespace spdlog { | |||
| class formatter; | |||
| namespace sinks { | |||
| class sink; | |||
| } | |||
| #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) | |||
| using filename_t = std::wstring; | |||
| #define SPDLOG_FILENAME_T(s) L##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<sinks::sink>; | |||
| using sinks_init_list = std::initializer_list<sink_ptr>; | |||
| using err_handler = std::function<void(const std::string &err_msg)>; | |||
| using string_view_t = fmt::basic_string_view<char>; | |||
| using wstring_view_t = fmt::basic_string_view<wchar_t>; | |||
| using memory_buf_t = fmt::basic_memory_buffer<char, 250>; | |||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| #ifndef _WIN32 | |||
| #error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||
| #else | |||
| template<typename T> | |||
| struct is_convertible_to_wstring_view : std::is_convertible<T, wstring_view_t> | |||
| {}; | |||
| #endif // _WIN32 | |||
| #else | |||
| template<typename> | |||
| struct is_convertible_to_wstring_view : std::false_type | |||
| {}; | |||
| #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| #if defined(SPDLOG_NO_ATOMIC_LEVELS) | |||
| using level_t = details::null_atomic_int; | |||
| #else | |||
| using level_t = std::atomic<int>; | |||
| #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 | |||
| { | |||
| 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, | |||
| }; | |||
| #if !defined(SPDLOG_LEVEL_NAMES) | |||
| #define SPDLOG_LEVEL_NAMES \ | |||
| { \ | |||
| "trace", "debug", "info", "warning", "error", "critical", "off" \ | |||
| } | |||
| #endif | |||
| #if !defined(SPDLOG_SHORT_LEVEL_NAMES) | |||
| #define SPDLOG_SHORT_LEVEL_NAMES \ | |||
| { \ | |||
| "T", "D", "I", "W", "E", "C", "O" \ | |||
| } | |||
| #endif | |||
| string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||
| const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT; | |||
| spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT; | |||
| using level_hasher = std::hash<int>; | |||
| } // 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_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_; | |||
| }; | |||
| 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}; | |||
| }; | |||
| namespace details { | |||
| // make_unique support for pre c++14 | |||
| #if __cplusplus >= 201402L // C++14 and beyond | |||
| using std::make_unique; | |||
| #else | |||
| template<typename T, typename... Args> | |||
| std::unique_ptr<T> make_unique(Args &&... args) | |||
| { | |||
| static_assert(!std::is_array<T>::value, "arrays not supported"); | |||
| return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); | |||
| } | |||
| #endif | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "common-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,74 @@ | |||
| // 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 "spdlog/details/backtracer.h" | |||
| #endif | |||
| namespace spdlog { | |||
| namespace details { | |||
| SPDLOG_INLINE backtracer::backtracer(const backtracer &other) | |||
| { | |||
| std::lock_guard<std::mutex> lock(other.mutex_); | |||
| enabled_ = other.enabled(); | |||
| messages_ = other.messages_; | |||
| } | |||
| SPDLOG_INLINE backtracer::backtracer(backtracer &&other) SPDLOG_NOEXCEPT | |||
| { | |||
| std::lock_guard<std::mutex> lock(other.mutex_); | |||
| enabled_ = other.enabled(); | |||
| messages_ = std::move(other.messages_); | |||
| } | |||
| SPDLOG_INLINE backtracer &backtracer::operator=(backtracer other) | |||
| { | |||
| std::lock_guard<std::mutex> lock(mutex_); | |||
| enabled_ = other.enabled(); | |||
| messages_ = other.messages_; | |||
| return *this; | |||
| } | |||
| SPDLOG_INLINE void backtracer::enable(size_t size) | |||
| { | |||
| std::lock_guard<std::mutex> lock{mutex_}; | |||
| enabled_.store(true, std::memory_order_relaxed); | |||
| messages_ = circular_q<log_msg_buffer>{size}; | |||
| } | |||
| SPDLOG_INLINE void backtracer::disable() | |||
| { | |||
| std::lock_guard<std::mutex> lock{mutex_}; | |||
| enabled_.store(false, std::memory_order_relaxed); | |||
| } | |||
| SPDLOG_INLINE bool backtracer::enabled() const | |||
| { | |||
| return enabled_.load(std::memory_order_relaxed); | |||
| } | |||
| SPDLOG_INLINE backtracer::operator bool() const | |||
| { | |||
| return enabled(); | |||
| } | |||
| SPDLOG_INLINE void backtracer::push_back(const log_msg &msg) | |||
| { | |||
| std::lock_guard<std::mutex> lock{mutex_}; | |||
| messages_.push_back(log_msg_buffer{msg}); | |||
| } | |||
| // pop all items in the q and apply the given fun on each of them. | |||
| SPDLOG_INLINE void backtracer::foreach_pop(std::function<void(const details::log_msg &)> fun) | |||
| { | |||
| std::lock_guard<std::mutex> lock{mutex_}; | |||
| while (!messages_.empty()) | |||
| { | |||
| auto &front_msg = messages_.front(); | |||
| fun(front_msg); | |||
| messages_.pop_front(); | |||
| } | |||
| } | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,46 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/log_msg_buffer.h" | |||
| #include "spdlog/details/circular_q.h" | |||
| #include <atomic> | |||
| #include <mutex> | |||
| #include <functional> | |||
| // Store log messages in circular buffer. | |||
| // Useful for storing debug data in case of error/warning happens. | |||
| namespace spdlog { | |||
| namespace details { | |||
| class backtracer | |||
| { | |||
| mutable std::mutex mutex_; | |||
| std::atomic<bool> enabled_{false}; | |||
| circular_q<log_msg_buffer> 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; | |||
| explicit operator bool() const; | |||
| void push_back(const log_msg &msg); | |||
| // pop all items in the q and apply the given fun on each of them. | |||
| void foreach_pop(std::function<void(const details::log_msg &)> fun); | |||
| }; | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "backtracer-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,119 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| // cirucal q view of std::vector. | |||
| #pragma once | |||
| #include <vector> | |||
| namespace spdlog { | |||
| namespace details { | |||
| template<typename T> | |||
| class circular_q | |||
| { | |||
| size_t max_items_ = 0; | |||
| typename std::vector<T>::size_type head_ = 0; | |||
| typename std::vector<T>::size_type tail_ = 0; | |||
| size_t overrun_counter_ = 0; | |||
| std::vector<T> 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_]; | |||
| } | |||
| // 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_; | |||
| } | |||
| 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 | |||
| @@ -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 "spdlog/details/null_mutex.h" | |||
| #include <mutex> | |||
| 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 | |||
| @@ -0,0 +1,133 @@ | |||
| // 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 "spdlog/details/file_helper.h" | |||
| #endif | |||
| #include "spdlog/details/os.h" | |||
| #include "spdlog/common.h" | |||
| #include <cerrno> | |||
| #include <chrono> | |||
| #include <cstdio> | |||
| #include <string> | |||
| #include <thread> | |||
| #include <tuple> | |||
| namespace spdlog { | |||
| namespace details { | |||
| SPDLOG_INLINE file_helper::~file_helper() | |||
| { | |||
| close(); | |||
| } | |||
| SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate) | |||
| { | |||
| close(); | |||
| auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); | |||
| _filename = fname; | |||
| for (int tries = 0; tries < open_tries; ++tries) | |||
| { | |||
| if (!os::fopen_s(&fd_, fname, mode)) | |||
| { | |||
| return; | |||
| } | |||
| details::os::sleep_for_millis(open_interval); | |||
| } | |||
| SPDLOG_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()) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("Failed re opening file - was not opened before")); | |||
| } | |||
| open(_filename, truncate); | |||
| } | |||
| SPDLOG_INLINE void file_helper::flush() | |||
| { | |||
| std::fflush(fd_); | |||
| } | |||
| SPDLOG_INLINE void file_helper::close() | |||
| { | |||
| if (fd_ != nullptr) | |||
| { | |||
| std::fclose(fd_); | |||
| fd_ = nullptr; | |||
| } | |||
| } | |||
| SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) | |||
| { | |||
| size_t msg_size = buf.size(); | |||
| auto data = buf.data(); | |||
| if (std::fwrite(data, 1, msg_size, fd_) != msg_size) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno)); | |||
| } | |||
| } | |||
| SPDLOG_INLINE size_t file_helper::size() const | |||
| { | |||
| if (fd_ == nullptr) | |||
| { | |||
| SPDLOG_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; | |||
| } | |||
| SPDLOG_INLINE bool file_helper::file_exists(const filename_t &fname) | |||
| { | |||
| return os::file_exists(fname); | |||
| } | |||
| // | |||
| // 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<filename_t, filename_t> 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 casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" | |||
| auto folder_index = fname.rfind(details::os::folder_sep); | |||
| 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 | |||
| @@ -0,0 +1,60 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/common.h" | |||
| #include <tuple> | |||
| 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 file_helper | |||
| { | |||
| public: | |||
| explicit file_helper() = default; | |||
| 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 close(); | |||
| void write(const memory_buf_t &buf); | |||
| size_t size() const; | |||
| const filename_t &filename() const; | |||
| static bool file_exists(const filename_t &fname); | |||
| // | |||
| // 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<filename_t, filename_t> split_by_extension(const filename_t &fname); | |||
| private: | |||
| const int open_tries = 5; | |||
| const int open_interval = 10; | |||
| std::FILE *fd_{nullptr}; | |||
| filename_t _filename; | |||
| }; | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "file_helper-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,111 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include <chrono> | |||
| #include <type_traits> | |||
| #include "spdlog/fmt/fmt.h" | |||
| #include "spdlog/common.h" | |||
| // Some fmt helpers to efficiently format and pad ints and strings | |||
| namespace spdlog { | |||
| namespace details { | |||
| namespace fmt_helper { | |||
| inline spdlog::string_view_t to_string_view(const memory_buf_t &buf) SPDLOG_NOEXCEPT | |||
| { | |||
| return spdlog::string_view_t{buf.data(), buf.size()}; | |||
| } | |||
| inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest) | |||
| { | |||
| auto *buf_ptr = view.data(); | |||
| if (buf_ptr != nullptr) | |||
| { | |||
| dest.append(buf_ptr, buf_ptr + view.size()); | |||
| } | |||
| } | |||
| template<typename T> | |||
| inline void append_int(T n, memory_buf_t &dest) | |||
| { | |||
| fmt::format_int i(n); | |||
| dest.append(i.data(), i.data() + i.size()); | |||
| } | |||
| template<typename T> | |||
| inline unsigned count_digits(T n) | |||
| { | |||
| using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; | |||
| return static_cast<unsigned>(fmt::internal::count_digits(static_cast<count_type>(n))); | |||
| } | |||
| inline void pad2(int n, memory_buf_t &dest) | |||
| { | |||
| if (n > 99) | |||
| { | |||
| append_int(n, dest); | |||
| } | |||
| else if (n > 9) // 10-99 | |||
| { | |||
| dest.push_back(static_cast<char>('0' + n / 10)); | |||
| dest.push_back(static_cast<char>('0' + n % 10)); | |||
| } | |||
| else if (n >= 0) // 0-9 | |||
| { | |||
| dest.push_back('0'); | |||
| dest.push_back(static_cast<char>('0' + n)); | |||
| } | |||
| else // negatives (unlikely, but just in case, let fmt deal with it) | |||
| { | |||
| fmt::format_to(dest, "{:02}", n); | |||
| } | |||
| } | |||
| template<typename T> | |||
| inline void pad_uint(T n, unsigned int width, memory_buf_t &dest) | |||
| { | |||
| static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T"); | |||
| auto digits = count_digits(n); | |||
| if (width > digits) | |||
| { | |||
| const char *zeroes = "0000000000000000000"; | |||
| dest.append(zeroes, zeroes + width - digits); | |||
| } | |||
| append_int(n, dest); | |||
| } | |||
| template<typename T> | |||
| inline void pad3(T n, memory_buf_t &dest) | |||
| { | |||
| pad_uint(n, 3, dest); | |||
| } | |||
| template<typename T> | |||
| inline void pad6(T n, memory_buf_t &dest) | |||
| { | |||
| pad_uint(n, 6, dest); | |||
| } | |||
| template<typename T> | |||
| 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<std::milliseconds>(tp) -> will return the millis part of the second | |||
| template<typename ToDuration> | |||
| 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<seconds>(duration); | |||
| return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); | |||
| } | |||
| } // namespace fmt_helper | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,34 @@ | |||
| // 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 "spdlog/details/log_msg.h" | |||
| #endif | |||
| #include "spdlog/details/os.h" | |||
| namespace spdlog { | |||
| namespace details { | |||
| SPDLOG_INLINE log_msg::log_msg(spdlog::source_loc loc, string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||
| : logger_name(logger_name) | |||
| , level(lvl) | |||
| #ifndef SPDLOG_NO_DATETIME | |||
| , time(os::now()) | |||
| #endif | |||
| #ifndef SPDLOG_NO_THREAD_ID | |||
| , thread_id(os::thread_id()) | |||
| #endif | |||
| , source(loc) | |||
| , payload(msg) | |||
| {} | |||
| SPDLOG_INLINE log_msg::log_msg(string_view_t logger_name, spdlog::level::level_enum lvl, spdlog::string_view_t msg) | |||
| : log_msg(source_loc{}, logger_name, lvl, msg) | |||
| {} | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -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 "spdlog/common.h" | |||
| #include <string> | |||
| namespace spdlog { | |||
| namespace details { | |||
| struct log_msg | |||
| { | |||
| log_msg() = default; | |||
| 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; | |||
| 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 | |||
| @@ -0,0 +1,60 @@ | |||
| // 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 "spdlog/details/log_msg_buffer.h" | |||
| #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) | |||
| : log_msg{std::move(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) | |||
| { | |||
| log_msg::operator=(std::move(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 | |||
| @@ -0,0 +1,33 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/log_msg.h" | |||
| 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 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); | |||
| log_msg_buffer &operator=(const log_msg_buffer &other); | |||
| log_msg_buffer &operator=(log_msg_buffer &&other); | |||
| }; | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "log_msg_buffer-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,120 @@ | |||
| // 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(..) - will return immediately with false if no room left in | |||
| // the queue. | |||
| // dequeue_for(..) - will block until the queue is not empty or timeout have | |||
| // passed. | |||
| #include "spdlog/details/circular_q.h" | |||
| #include <condition_variable> | |||
| #include <mutex> | |||
| namespace spdlog { | |||
| namespace details { | |||
| template<typename T> | |||
| 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<std::mutex> 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<std::mutex> lock(queue_mutex_); | |||
| q_.push_back(std::move(item)); | |||
| } | |||
| push_cv_.notify_one(); | |||
| } | |||
| // try to dequeue item. if no item found. wait upto timeout and try again | |||
| // Return true, if succeeded dequeue item, false otherwise | |||
| bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||
| { | |||
| { | |||
| std::unique_lock<std::mutex> 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; | |||
| } | |||
| #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<std::mutex> 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<std::mutex> lock(queue_mutex_); | |||
| q_.push_back(std::move(item)); | |||
| push_cv_.notify_one(); | |||
| } | |||
| // try to dequeue item. if no item found. wait upto timeout and try again | |||
| // Return true, if succeeded dequeue item, false otherwise | |||
| bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) | |||
| { | |||
| std::unique_lock<std::mutex> 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; | |||
| } | |||
| #endif | |||
| size_t overrun_counter() | |||
| { | |||
| std::unique_lock<std::mutex> lock(queue_mutex_); | |||
| return q_.overrun_counter(); | |||
| } | |||
| private: | |||
| std::mutex queue_mutex_; | |||
| std::condition_variable push_cv_; | |||
| std::condition_variable pop_cv_; | |||
| spdlog::details::circular_q<T> q_; | |||
| }; | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,49 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include <atomic> | |||
| #include <utility> | |||
| // null, no cost dummy "mutex" and dummy "atomic" int | |||
| namespace spdlog { | |||
| namespace details { | |||
| struct null_mutex | |||
| { | |||
| void lock() const {} | |||
| void unlock() const {} | |||
| bool try_lock() const | |||
| { | |||
| return true; | |||
| } | |||
| }; | |||
| struct null_atomic_int | |||
| { | |||
| int value; | |||
| 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 | |||
| @@ -0,0 +1,465 @@ | |||
| // 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 "spdlog/details/os.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include <algorithm> | |||
| #include <chrono> | |||
| #include <cstdio> | |||
| #include <cstdlib> | |||
| #include <cstring> | |||
| #include <ctime> | |||
| #include <string> | |||
| #include <thread> | |||
| #include <array> | |||
| #include <sys/stat.h> | |||
| #include <sys/types.h> | |||
| #ifdef _WIN32 | |||
| #ifndef NOMINMAX | |||
| #define NOMINMAX // prevent windows redefining min/max | |||
| #endif | |||
| #ifndef WIN32_LEAN_AND_MEAN | |||
| #define WIN32_LEAN_AND_MEAN | |||
| #endif | |||
| #include <io.h> // _get_osfhandle and _isatty support | |||
| #include <process.h> // _get_pid support | |||
| #include <windows.h> | |||
| #ifdef __MINGW32__ | |||
| #include <share.h> | |||
| #endif | |||
| #if defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES) | |||
| #include <limits> | |||
| #endif | |||
| #else // unix | |||
| #include <fcntl.h> | |||
| #include <unistd.h> | |||
| #ifdef __linux__ | |||
| #include <sys/syscall.h> //Use gettid() syscall under linux to get thread id | |||
| #elif defined(_AIX) | |||
| #include <pthread.h> // for pthread_getthreadid_np | |||
| #elif defined(__DragonFly__) || defined(__FreeBSD__) | |||
| #include <pthread_np.h> // for pthread_getthreadid_np | |||
| #elif defined(__NetBSD__) | |||
| #include <lwp.h> // for _lwp_self | |||
| #elif defined(__sun) | |||
| #include <thread.h> // for thr_self | |||
| #endif | |||
| #endif // unix | |||
| #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<log_clock, typename log_clock::duration>( | |||
| std::chrono::duration_cast<typename log_clock::duration>(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); | |||
| } | |||
| SPDLOG_INLINE void prevent_child_fd(FILE *f) | |||
| { | |||
| #ifdef _WIN32 | |||
| #if !defined(__cplusplus_winrt) | |||
| auto file_handle = reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(f))); | |||
| if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) | |||
| SPDLOG_THROW(spdlog_ex("SetHandleInformation failed", errno)); | |||
| #endif | |||
| #else | |||
| auto fd = fileno(f); | |||
| if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("fcntl with FD_CLOEXEC failed", errno)); | |||
| } | |||
| #endif | |||
| } | |||
| // 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 | |||
| #else // unix | |||
| *fp = fopen((filename.c_str()), mode.c_str()); | |||
| #endif | |||
| #ifdef SPDLOG_PREVENT_CHILD_FD | |||
| if (*fp != nullptr) | |||
| { | |||
| prevent_child_fd(*fp); | |||
| } | |||
| #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 file_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 file exists | |||
| SPDLOG_INLINE bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT | |||
| { | |||
| #ifdef _WIN32 | |||
| #ifdef SPDLOG_WCHAR_FILENAMES | |||
| auto attribs = GetFileAttributesW(filename.c_str()); | |||
| #else | |||
| auto attribs = GetFileAttributesA(filename.c_str()); | |||
| #endif | |||
| return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); | |||
| #else // common linux/unix all have the stat system call | |||
| struct stat buffer; | |||
| return (::stat(filename.c_str(), &buffer) == 0); | |||
| #endif | |||
| } | |||
| // Return file size according to open FILE* object | |||
| SPDLOG_INLINE size_t filesize(FILE *f) | |||
| { | |||
| if (f == nullptr) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("Failed getting file size. fd is null")); | |||
| } | |||
| #if defined(_WIN32) && !defined(__CYGWIN__) | |||
| int fd = _fileno(f); | |||
| #if _WIN64 // 64 bits | |||
| __int64 ret = _filelengthi64(fd); | |||
| if (ret >= 0) | |||
| { | |||
| return static_cast<size_t>(ret); | |||
| } | |||
| #else // windows 32 bits | |||
| long ret = _filelength(fd); | |||
| if (ret >= 0) | |||
| { | |||
| return static_cast<size_t>(ret); | |||
| } | |||
| #endif | |||
| #else // unix | |||
| int fd = fileno(f); | |||
| // 64 bits(but not in osx or cygwin, where fstat64 is deprecated) | |||
| #if (defined(__linux__) || defined(__sun) || defined(_AIX)) && (defined(__LP64__) || defined(_LP64)) | |||
| struct stat64 st; | |||
| if (::fstat64(fd, &st) == 0) | |||
| { | |||
| return static_cast<size_t>(st.st_size); | |||
| } | |||
| #else // unix 32 bits or cygwin | |||
| struct stat st; | |||
| if (::fstat(fd, &st) == 0) | |||
| { | |||
| return static_cast<size_t>(st.st_size); | |||
| } | |||
| #endif | |||
| #endif | |||
| SPDLOG_THROW(spdlog_ex("Failed getting file size from fd", errno)); | |||
| } | |||
| // Return utc offset in minutes or throw spdlog_ex on failure | |||
| SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) | |||
| { | |||
| #ifdef _WIN32 | |||
| #if _WIN32_WINNT < _WIN32_WINNT_WS08 | |||
| TIME_ZONE_INFORMATION tzinfo; | |||
| auto rv = GetTimeZoneInformation(&tzinfo); | |||
| #else | |||
| DYNAMIC_TIME_ZONE_INFORMATION tzinfo; | |||
| auto rv = GetDynamicTimeZoneInformation(&tzinfo); | |||
| #endif | |||
| if (rv == TIME_ZONE_ID_INVALID) | |||
| SPDLOG_THROW(spdlog::spdlog_ex("Failed getting timezone info. ", errno)); | |||
| int offset = -tzinfo.Bias; | |||
| if (tm.tm_isdst) | |||
| { | |||
| offset -= tzinfo.DaylightBias; | |||
| } | |||
| else | |||
| { | |||
| offset -= tzinfo.StandardBias; | |||
| } | |||
| return offset; | |||
| #else | |||
| #if defined(sun) || defined(__sun) || defined(_AIX) | |||
| // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris | |||
| struct helper | |||
| { | |||
| static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) | |||
| { | |||
| int local_year = localtm.tm_year + (1900 - 1); | |||
| int gmt_year = gmtm.tm_year + (1900 - 1); | |||
| long int days = ( | |||
| // difference in day of year | |||
| localtm.tm_yday - | |||
| gmtm.tm_yday | |||
| // + intervening leap days | |||
| + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + | |||
| ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) | |||
| // + difference in years * 365 */ | |||
| + (long int)(local_year - gmt_year) * 365); | |||
| long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); | |||
| long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); | |||
| long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); | |||
| return secs; | |||
| } | |||
| }; | |||
| auto offset_seconds = helper::calculate_gmt_offset(tm); | |||
| #else | |||
| auto offset_seconds = tm.tm_gmtoff; | |||
| #endif | |||
| return static_cast<int>(offset_seconds / 60); | |||
| #endif | |||
| } | |||
| // 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<size_t>(::GetCurrentThreadId()); | |||
| #elif defined(__linux__) | |||
| #if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) | |||
| #define SYS_gettid __NR_gettid | |||
| #endif | |||
| return static_cast<size_t>(syscall(SYS_gettid)); | |||
| #elif defined(_AIX) || defined(__DragonFly__) || defined(__FreeBSD__) | |||
| return static_cast<size_t>(pthread_getthreadid_np()); | |||
| #elif defined(__NetBSD__) | |||
| return static_cast<size_t>(_lwp_self()); | |||
| #elif defined(__OpenBSD__) | |||
| return static_cast<size_t>(getthrid()); | |||
| #elif defined(__sun) | |||
| return static_cast<size_t>(thr_self()); | |||
| #elif __APPLE__ | |||
| uint64_t tid; | |||
| pthread_threadid_np(nullptr, &tid); | |||
| return static_cast<size_t>(tid); | |||
| #else // Default to standard C++11 (other Unix) | |||
| return static_cast<size_t>(std::hash<std::thread::id>()(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(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 fmt::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 static_cast<int>(::GetCurrentProcessId()); | |||
| #else | |||
| return static_cast<int>(::getpid()); | |||
| #endif | |||
| } | |||
| // Determine if the terminal supports colors | |||
| // Source: https://github.com/agauniyal/rang/ | |||
| SPDLOG_INLINE bool is_color_terminal() SPDLOG_NOEXCEPT | |||
| { | |||
| #ifdef _WIN32 | |||
| return true; | |||
| #else | |||
| static constexpr std::array<const char *, 14> Terms = { | |||
| {"ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}}; | |||
| const char *env_p = std::getenv("TERM"); | |||
| if (env_p == nullptr) | |||
| { | |||
| return false; | |||
| } | |||
| static const bool result = | |||
| std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); | |||
| return result; | |||
| #endif | |||
| } | |||
| // Detrmine 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<size_t>(std::numeric_limits<int>::max())) | |||
| { | |||
| SPDLOG_THROW(spdlog::spdlog_ex("UTF-16 string is too big to be converted to UTF-8")); | |||
| } | |||
| int wstr_size = static_cast<int>(wstr.size()); | |||
| if (wstr_size == 0) | |||
| { | |||
| target.resize(0); | |||
| return; | |||
| } | |||
| int result_size = static_cast<int>(target.capacity()); | |||
| if ((wstr_size + 1) * 2 > 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; | |||
| } | |||
| } | |||
| SPDLOG_THROW(spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()))); | |||
| } | |||
| #endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||
| } // namespace os | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,98 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/common.h" | |||
| #include <ctime> // std::time_t | |||
| namespace spdlog { | |||
| namespace details { | |||
| namespace os { | |||
| spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT; | |||
| std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||
| std::tm localtime() SPDLOG_NOEXCEPT; | |||
| std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT; | |||
| 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 | |||
| #ifdef _WIN32 | |||
| const char folder_sep = '\\'; | |||
| #else | |||
| SPDLOG_CONSTEXPR static const char folder_sep = '/'; | |||
| #endif | |||
| void prevent_child_fd(FILE *f); | |||
| // fopen_s on non windows for writing | |||
| bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode); | |||
| // Remove filename. return 0 on success | |||
| 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) | |||
| int remove_if_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||
| int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT; | |||
| // Return if file exists. | |||
| bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT; | |||
| // Return file size according to open FILE* object | |||
| size_t filesize(FILE *f); | |||
| // Return utc offset in minutes or throw spdlog_ex on failure | |||
| 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) | |||
| size_t _thread_id() SPDLOG_NOEXCEPT; | |||
| // Return current thread id as size_t (from thread local storage) | |||
| 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 | |||
| void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT; | |||
| std::string filename_to_str(const filename_t &filename); | |||
| int pid() SPDLOG_NOEXCEPT; | |||
| // Determine if the terminal supports colors | |||
| // Source: https://github.com/agauniyal/rang/ | |||
| bool is_color_terminal() SPDLOG_NOEXCEPT; | |||
| // Detrmine if the terminal attached | |||
| // Source: https://github.com/agauniyal/rang/ | |||
| bool in_terminal(FILE *file) SPDLOG_NOEXCEPT; | |||
| #if (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) | |||
| void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target); | |||
| #endif | |||
| } // namespace os | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "os-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,99 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| #include "spdlog/details/os.h" | |||
| #include "spdlog/formatter.h" | |||
| #include <chrono> | |||
| #include <ctime> | |||
| #include <memory> | |||
| #include <string> | |||
| #include <vector> | |||
| namespace spdlog { | |||
| namespace details { | |||
| // padding information. | |||
| struct padding_info | |||
| { | |||
| enum pad_side | |||
| { | |||
| left, | |||
| right, | |||
| center | |||
| }; | |||
| padding_info() = default; | |||
| padding_info(size_t width, padding_info::pad_side side) | |||
| : width_(width) | |||
| , side_(side) | |||
| {} | |||
| bool enabled() const | |||
| { | |||
| return width_ != 0; | |||
| } | |||
| const size_t width_ = 0; | |||
| const pad_side side_ = left; | |||
| }; | |||
| class 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 pattern_formatter final : public formatter | |||
| { | |||
| public: | |||
| explicit pattern_formatter( | |||
| std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol); | |||
| // 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<formatter> clone() const override; | |||
| void format(const details::log_msg &msg, memory_buf_t &dest) override; | |||
| private: | |||
| std::string pattern_; | |||
| std::string eol_; | |||
| pattern_time_type pattern_time_type_; | |||
| std::tm cached_tm_; | |||
| std::chrono::seconds last_log_secs_; | |||
| std::vector<std::unique_ptr<details::flag_formatter>> formatters_; | |||
| std::tm get_time_(const details::log_msg &msg); | |||
| template<typename Padder> | |||
| 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. | |||
| 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 | |||
| @@ -0,0 +1,49 @@ | |||
| // 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 "spdlog/details/periodic_worker.h" | |||
| #endif | |||
| namespace spdlog { | |||
| namespace details { | |||
| SPDLOG_INLINE periodic_worker::periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval) | |||
| { | |||
| active_ = (interval > std::chrono::seconds::zero()); | |||
| if (!active_) | |||
| { | |||
| return; | |||
| } | |||
| worker_thread_ = std::thread([this, callback_fun, interval]() { | |||
| for (;;) | |||
| { | |||
| std::unique_lock<std::mutex> lock(this->mutex_); | |||
| if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) | |||
| { | |||
| return; // active_ == false, so exit this thread | |||
| } | |||
| callback_fun(); | |||
| } | |||
| }); | |||
| } | |||
| // stop the worker thread and join it | |||
| SPDLOG_INLINE periodic_worker::~periodic_worker() | |||
| { | |||
| if (worker_thread_.joinable()) | |||
| { | |||
| { | |||
| std::lock_guard<std::mutex> lock(mutex_); | |||
| active_ = false; | |||
| } | |||
| cv_.notify_one(); | |||
| worker_thread_.join(); | |||
| } | |||
| } | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,40 @@ | |||
| // 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 <chrono> | |||
| #include <condition_variable> | |||
| #include <functional> | |||
| #include <mutex> | |||
| #include <thread> | |||
| namespace spdlog { | |||
| namespace details { | |||
| class periodic_worker | |||
| { | |||
| public: | |||
| periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval); | |||
| 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 | |||
| @@ -0,0 +1,284 @@ | |||
| // 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 "spdlog/details/registry.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/periodic_worker.h" | |||
| #include "spdlog/logger.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #ifndef SPDLOG_DISABLE_DEFAULT_LOGGER | |||
| // support for the default stdout color logger | |||
| #ifdef _WIN32 | |||
| #include "spdlog/sinks/wincolor_sink.h" | |||
| #else | |||
| #include "spdlog/sinks/ansicolor_sink.h" | |||
| #endif | |||
| #endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||
| #include <chrono> | |||
| #include <functional> | |||
| #include <memory> | |||
| #include <string> | |||
| #include <unordered_map> | |||
| 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<sinks::wincolor_stdout_sink_mt>(); | |||
| #else | |||
| auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); | |||
| #endif | |||
| const char *default_logger_name = ""; | |||
| default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); | |||
| loggers_[default_logger_name] = default_logger_; | |||
| #endif // SPDLOG_DISABLE_DEFAULT_LOGGER | |||
| } | |||
| SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| register_logger_(std::move(new_logger)); | |||
| } | |||
| SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| new_logger->set_formatter(formatter_->clone()); | |||
| if (err_handler_) | |||
| { | |||
| new_logger->set_error_handler(err_handler_); | |||
| } | |||
| new_logger->set_level(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<logger> registry::get(const std::string &logger_name) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| auto found = loggers_.find(logger_name); | |||
| return found == loggers_.end() ? nullptr : found->second; | |||
| } | |||
| SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() | |||
| { | |||
| std::lock_guard<std::mutex> 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. | |||
| // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||
| SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| // remove previous default logger from the map | |||
| if (default_logger_ != nullptr) | |||
| { | |||
| loggers_.erase(default_logger_->name()); | |||
| } | |||
| 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<thread_pool> tp) | |||
| { | |||
| std::lock_guard<std::recursive_mutex> lock(tp_mutex_); | |||
| tp_ = std::move(tp); | |||
| } | |||
| SPDLOG_INLINE std::shared_ptr<thread_pool> registry::get_tp() | |||
| { | |||
| std::lock_guard<std::recursive_mutex> 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> formatter) | |||
| { | |||
| std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(logger_map_mutex_); | |||
| for (auto &l : loggers_) | |||
| { | |||
| l.second->set_level(log_level); | |||
| } | |||
| level_ = log_level; | |||
| } | |||
| SPDLOG_INLINE void registry::flush_on(level::level_enum log_level) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| for (auto &l : loggers_) | |||
| { | |||
| l.second->flush_on(log_level); | |||
| } | |||
| flush_level_ = log_level; | |||
| } | |||
| SPDLOG_INLINE void registry::flush_every(std::chrono::seconds interval) | |||
| { | |||
| std::lock_guard<std::mutex> lock(flusher_mutex_); | |||
| std::function<void()> clbk = std::bind(®istry::flush_all, this); | |||
| periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); | |||
| } | |||
| SPDLOG_INLINE void registry::set_error_handler(void (*handler)(const std::string &msg)) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| for (auto &l : loggers_) | |||
| { | |||
| l.second->set_error_handler(handler); | |||
| } | |||
| err_handler_ = handler; | |||
| } | |||
| SPDLOG_INLINE void registry::apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| for (auto &l : loggers_) | |||
| { | |||
| fun(l.second); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void registry::flush_all() | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| for (auto &l : loggers_) | |||
| { | |||
| l.second->flush(); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void registry::drop(const std::string &logger_name) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| loggers_.erase(logger_name); | |||
| if (default_logger_ && default_logger_->name() == logger_name) | |||
| { | |||
| default_logger_.reset(); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void registry::drop_all() | |||
| { | |||
| std::lock_guard<std::mutex> 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<std::mutex> lock(flusher_mutex_); | |||
| periodic_flusher_.reset(); | |||
| } | |||
| drop_all(); | |||
| { | |||
| std::lock_guard<std::recursive_mutex> 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_regsistration) | |||
| { | |||
| std::lock_guard<std::mutex> lock(logger_map_mutex_); | |||
| automatic_registration_ = automatic_regsistration; | |||
| } | |||
| SPDLOG_INLINE registry ®istry::instance() | |||
| { | |||
| static registry s_instance; | |||
| return s_instance; | |||
| } | |||
| SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) | |||
| { | |||
| if (loggers_.find(logger_name) != loggers_.end()) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("logger with name '" + logger_name + "' already exists")); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) | |||
| { | |||
| auto logger_name = new_logger->name(); | |||
| throw_if_exists_(logger_name); | |||
| loggers_[logger_name] = std::move(new_logger); | |||
| } | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,109 @@ | |||
| // 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 "spdlog/common.h" | |||
| #include <chrono> | |||
| #include <functional> | |||
| #include <memory> | |||
| #include <string> | |||
| #include <unordered_map> | |||
| #include <mutex> | |||
| namespace spdlog { | |||
| class logger; | |||
| namespace details { | |||
| class thread_pool; | |||
| class periodic_worker; | |||
| class registry | |||
| { | |||
| public: | |||
| registry(const registry &) = delete; | |||
| registry &operator=(const registry &) = delete; | |||
| void register_logger(std::shared_ptr<logger> new_logger); | |||
| void initialize_logger(std::shared_ptr<logger> new_logger); | |||
| std::shared_ptr<logger> get(const std::string &logger_name); | |||
| std::shared_ptr<logger> 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. | |||
| // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. | |||
| void set_default_logger(std::shared_ptr<logger> new_default_logger); | |||
| void set_tp(std::shared_ptr<thread_pool> tp); | |||
| std::shared_ptr<thread_pool> get_tp(); | |||
| // Set global formatter. Each sink in each logger will get a clone of this object | |||
| void set_formatter(std::unique_ptr<formatter> 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); | |||
| void flush_every(std::chrono::seconds interval); | |||
| void set_error_handler(void (*handler)(const std::string &msg)); | |||
| void apply_all(const std::function<void(const std::shared_ptr<logger>)> &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_regsistration); | |||
| static registry &instance(); | |||
| private: | |||
| registry(); | |||
| ~registry() = default; | |||
| void throw_if_exists_(const std::string &logger_name); | |||
| void register_logger_(std::shared_ptr<logger> new_logger); | |||
| std::mutex logger_map_mutex_, flusher_mutex_; | |||
| std::recursive_mutex tp_mutex_; | |||
| std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; | |||
| std::unique_ptr<formatter> formatter_; | |||
| level::level_enum level_ = level::info; | |||
| level::level_enum flush_level_ = level::off; | |||
| void (*err_handler_)(const std::string &msg); | |||
| std::shared_ptr<thread_pool> tp_; | |||
| std::unique_ptr<periodic_worker> periodic_flusher_; | |||
| std::shared_ptr<logger> 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 | |||
| @@ -0,0 +1,24 @@ | |||
| // 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<typename Sink, typename... SinkArgs> | |||
| static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) | |||
| { | |||
| auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); | |||
| auto new_logger = std::make_shared<spdlog::logger>(std::move(logger_name), std::move(sink)); | |||
| details::registry::instance().initialize_logger(new_logger); | |||
| return new_logger; | |||
| } | |||
| }; | |||
| } // namespace spdlog | |||
| @@ -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 "spdlog/details/thread_pool.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| namespace spdlog { | |||
| namespace details { | |||
| SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start) | |||
| : q_(q_max_items) | |||
| { | |||
| if (threads_n == 0 || threads_n > 1000) | |||
| { | |||
| SPDLOG_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_start(); | |||
| this->thread_pool::worker_loop_(); | |||
| }); | |||
| } | |||
| } | |||
| 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_ALL() {} | |||
| } | |||
| 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::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 | |||
| { | |||
| q_.enqueue_nowait(std::move(new_msg)); | |||
| } | |||
| } | |||
| void SPDLOG_INLINE thread_pool::worker_loop_() | |||
| { | |||
| while (process_next_msg_()) {}; | |||
| } | |||
| // process next message in the queue | |||
| // return true if this thread should still be active (while no terminate msg | |||
| // was received) | |||
| bool SPDLOG_INLINE thread_pool::process_next_msg_() | |||
| { | |||
| async_msg incoming_async_msg; | |||
| bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); | |||
| if (!dequeued) | |||
| { | |||
| return true; | |||
| } | |||
| 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 && "Unexpected async_msg_type"); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| } // namespace details | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,120 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/log_msg_buffer.h" | |||
| #include "spdlog/details/mpmc_blocking_q.h" | |||
| #include "spdlog/details/os.h" | |||
| #include <chrono> | |||
| #include <memory> | |||
| #include <thread> | |||
| #include <vector> | |||
| #include <functional> | |||
| namespace spdlog { | |||
| class async_logger; | |||
| namespace details { | |||
| using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; | |||
| enum class async_msg_type | |||
| { | |||
| log, | |||
| flush, | |||
| terminate | |||
| }; | |||
| #include "spdlog/details/log_msg_buffer.h" | |||
| // 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<log_msg_buffer *>(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 thread_pool | |||
| { | |||
| public: | |||
| using item_type = async_msg; | |||
| using q_type = details::mpmc_blocking_queue<item_type>; | |||
| thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start); | |||
| thread_pool(size_t q_max_items, size_t threads_n); | |||
| // message all threads to terminate gracefully 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(); | |||
| private: | |||
| q_type q_; | |||
| std::vector<std::thread> 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 | |||
| @@ -0,0 +1,175 @@ | |||
| // | |||
| // Copyright(c) 2015 Gabi Melman. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| // | |||
| #pragma once | |||
| // | |||
| // Support for logging binary data as hex | |||
| // 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. | |||
| // | |||
| // Examples: | |||
| // | |||
| // std::vector<char> 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))); | |||
| namespace spdlog { | |||
| namespace details { | |||
| template<typename It> | |||
| class bytes_range | |||
| { | |||
| public: | |||
| bytes_range(It range_begin, It range_end) | |||
| : begin_(range_begin) | |||
| , end_(range_end) | |||
| {} | |||
| It begin() const | |||
| { | |||
| return begin_; | |||
| } | |||
| It end() const | |||
| { | |||
| return end_; | |||
| } | |||
| private: | |||
| It begin_, end_; | |||
| }; | |||
| } // namespace details | |||
| // create a bytes_range that wraps the given container | |||
| template<typename Container> | |||
| inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container) | |||
| { | |||
| static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); | |||
| using Iter = typename Container::const_iterator; | |||
| return details::bytes_range<Iter>(std::begin(container), std::end(container)); | |||
| } | |||
| // create bytes_range from ranges | |||
| template<typename It> | |||
| inline details::bytes_range<It> to_hex(const It range_begin, const It range_end) | |||
| { | |||
| return details::bytes_range<It>(range_begin, range_end); | |||
| } | |||
| } // namespace spdlog | |||
| namespace fmt { | |||
| template<typename T> | |||
| struct formatter<spdlog::details::bytes_range<T>> | |||
| { | |||
| const std::size_t line_size = 100; | |||
| const char delimiter = ' '; | |||
| bool put_newlines = true; | |||
| bool put_delimiters = true; | |||
| bool use_uppercase = false; | |||
| bool put_positions = true; // position on start of each line | |||
| // parse the format string flags | |||
| template<typename ParseContext> | |||
| auto parse(ParseContext &ctx) -> decltype(ctx.begin()) | |||
| { | |||
| auto it = ctx.begin(); | |||
| while (*it && *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; | |||
| break; | |||
| } | |||
| ++it; | |||
| } | |||
| return it; | |||
| } | |||
| // format the given bytes range as hex | |||
| template<typename FormatContext, typename Container> | |||
| auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> 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; | |||
| std::size_t pos = 0; | |||
| std::size_t column = line_size; | |||
| #if FMT_VERSION < 60000 | |||
| auto inserter = ctx.begin(); | |||
| #else | |||
| auto inserter = ctx.out(); | |||
| #endif | |||
| for (auto &item : the_range) | |||
| { | |||
| auto ch = static_cast<unsigned char>(item); | |||
| pos++; | |||
| if (put_newlines && column >= line_size) | |||
| { | |||
| column = put_newline(inserter, pos); | |||
| // put first byte without delimiter in front of it | |||
| *inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||
| *inserter++ = hex_chars[ch & 0x0f]; | |||
| column += 2; | |||
| continue; | |||
| } | |||
| if (put_delimiters) | |||
| { | |||
| *inserter++ = delimiter; | |||
| ++column; | |||
| } | |||
| *inserter++ = hex_chars[(ch >> 4) & 0x0f]; | |||
| *inserter++ = hex_chars[ch & 0x0f]; | |||
| column += 2; | |||
| } | |||
| return inserter; | |||
| } | |||
| // put newline(and position header) | |||
| // return the next column | |||
| template<typename It> | |||
| std::size_t put_newline(It inserter, std::size_t pos) | |||
| { | |||
| #ifdef _WIN32 | |||
| *inserter++ = '\r'; | |||
| #endif | |||
| *inserter++ = '\n'; | |||
| if (put_positions) | |||
| { | |||
| fmt::format_to(inserter, "{:<04X}: ", pos - 1); | |||
| return 7; | |||
| } | |||
| else | |||
| { | |||
| return 1; | |||
| } | |||
| } | |||
| }; | |||
| } // namespace fmt | |||
| @@ -0,0 +1,27 @@ | |||
| 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. | |||
| @@ -0,0 +1,829 @@ | |||
| // 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_ | |||
| #include "format.h" | |||
| #include "locale.h" | |||
| #include <chrono> | |||
| #include <ctime> | |||
| #include <locale> | |||
| #include <sstream> | |||
| // enable safe chrono durations, unless explicitly disabled | |||
| #ifndef FMT_SAFE_DURATION_CAST | |||
| # define FMT_SAFE_DURATION_CAST 1 | |||
| #endif | |||
| #if FMT_SAFE_DURATION_CAST | |||
| # include "safe-duration-cast.h" | |||
| #endif | |||
| FMT_BEGIN_NAMESPACE | |||
| // Prevents expansion of a preceding token as a function-style macro. | |||
| // Usage: f FMT_NOMACRO() | |||
| #define FMT_NOMACRO | |||
| namespace internal { | |||
| inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } | |||
| inline null<> localtime_s(...) { return null<>(); } | |||
| inline null<> gmtime_r(...) { return null<>(); } | |||
| inline null<> gmtime_s(...) { return null<>(); } | |||
| } // namespace internal | |||
| // Thread-safe replacement for std::localtime | |||
| inline std::tm localtime(std::time_t time) { | |||
| struct dispatcher { | |||
| std::time_t time_; | |||
| std::tm tm_; | |||
| dispatcher(std::time_t t) : time_(t) {} | |||
| bool run() { | |||
| using namespace fmt::internal; | |||
| return handle(localtime_r(&time_, &tm_)); | |||
| } | |||
| bool handle(std::tm* tm) { return tm != nullptr; } | |||
| bool handle(internal::null<>) { | |||
| using namespace fmt::internal; | |||
| return fallback(localtime_s(&tm_, &time_)); | |||
| } | |||
| bool fallback(int res) { return res == 0; } | |||
| #if !FMT_MSC_VER | |||
| bool fallback(internal::null<>) { | |||
| using namespace fmt::internal; | |||
| std::tm* tm = std::localtime(&time_); | |||
| if (tm) tm_ = *tm; | |||
| return tm != nullptr; | |||
| } | |||
| #endif | |||
| }; | |||
| dispatcher lt(time); | |||
| // Too big time values may be unsupported. | |||
| if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); | |||
| return lt.tm_; | |||
| } | |||
| // Thread-safe replacement for std::gmtime | |||
| inline std::tm gmtime(std::time_t time) { | |||
| struct dispatcher { | |||
| std::time_t time_; | |||
| std::tm tm_; | |||
| dispatcher(std::time_t t) : time_(t) {} | |||
| bool run() { | |||
| using namespace fmt::internal; | |||
| return handle(gmtime_r(&time_, &tm_)); | |||
| } | |||
| bool handle(std::tm* tm) { return tm != nullptr; } | |||
| bool handle(internal::null<>) { | |||
| using namespace fmt::internal; | |||
| return fallback(gmtime_s(&tm_, &time_)); | |||
| } | |||
| bool fallback(int res) { return res == 0; } | |||
| #if !FMT_MSC_VER | |||
| bool fallback(internal::null<>) { | |||
| std::tm* tm = std::gmtime(&time_); | |||
| if (tm) tm_ = *tm; | |||
| return tm != nullptr; | |||
| } | |||
| #endif | |||
| }; | |||
| dispatcher gt(time); | |||
| // Too big time values may be unsupported. | |||
| if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); | |||
| return gt.tm_; | |||
| } | |||
| namespace internal { | |||
| inline std::size_t strftime(char* str, std::size_t count, const char* format, | |||
| const std::tm* time) { | |||
| return std::strftime(str, count, format, time); | |||
| } | |||
| inline std::size_t strftime(wchar_t* str, std::size_t count, | |||
| const wchar_t* format, const std::tm* time) { | |||
| return std::wcsftime(str, count, format, time); | |||
| } | |||
| } // namespace internal | |||
| template <typename Char> struct formatter<std::tm, Char> { | |||
| template <typename ParseContext> | |||
| auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||
| auto it = ctx.begin(); | |||
| if (it != ctx.end() && *it == ':') ++it; | |||
| auto end = it; | |||
| while (end != ctx.end() && *end != '}') ++end; | |||
| tm_format.reserve(internal::to_unsigned(end - it + 1)); | |||
| tm_format.append(it, end); | |||
| tm_format.push_back('\0'); | |||
| return end; | |||
| } | |||
| template <typename FormatContext> | |||
| auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { | |||
| basic_memory_buffer<Char> buf; | |||
| std::size_t start = buf.size(); | |||
| for (;;) { | |||
| std::size_t size = buf.capacity() - start; | |||
| std::size_t count = | |||
| internal::strftime(&buf[start], size, &tm_format[0], &tm); | |||
| if (count != 0) { | |||
| buf.resize(start + count); | |||
| break; | |||
| } | |||
| if (size >= tm_format.size() * 256) { | |||
| // If the buffer is 256 times larger than the format string, assume | |||
| // that `strftime` gives an empty result. There doesn't seem to be a | |||
| // better way to distinguish the two cases: | |||
| // https://github.com/fmtlib/fmt/issues/367 | |||
| break; | |||
| } | |||
| const std::size_t MIN_GROWTH = 10; | |||
| buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); | |||
| } | |||
| return std::copy(buf.begin(), buf.end(), ctx.out()); | |||
| } | |||
| basic_memory_buffer<Char> tm_format; | |||
| }; | |||
| namespace internal { | |||
| template <typename Period> FMT_CONSTEXPR const char* get_units() { | |||
| return nullptr; | |||
| } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::atto>() { return "as"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::femto>() { return "fs"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::pico>() { return "ps"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::nano>() { return "ns"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::micro>() { return "µs"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::milli>() { return "ms"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::centi>() { return "cs"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::deci>() { return "ds"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::ratio<1>>() { return "s"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::deca>() { return "das"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::hecto>() { return "hs"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::kilo>() { return "ks"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::mega>() { return "Ms"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::giga>() { return "Gs"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::tera>() { return "Ts"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::peta>() { return "Ps"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::exa>() { return "Es"; } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::ratio<60>>() { | |||
| return "m"; | |||
| } | |||
| template <> FMT_CONSTEXPR const char* get_units<std::ratio<3600>>() { | |||
| return "h"; | |||
| } | |||
| enum class numeric_system { | |||
| standard, | |||
| // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. | |||
| alternative | |||
| }; | |||
| // Parses a put_time-like format string and invokes handler actions. | |||
| template <typename Char, typename Handler> | |||
| FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, | |||
| const Char* end, | |||
| Handler&& handler) { | |||
| auto ptr = begin; | |||
| while (ptr != end) { | |||
| 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 '%': | |||
| 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; | |||
| } | |||
| // 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': | |||
| handler.on_abbr_month(); | |||
| break; | |||
| case 'B': | |||
| handler.on_full_month(); | |||
| break; | |||
| // Hour, minute, second: | |||
| case 'H': | |||
| handler.on_24_hour(numeric_system::standard); | |||
| break; | |||
| case 'I': | |||
| handler.on_12_hour(numeric_system::standard); | |||
| break; | |||
| case 'M': | |||
| handler.on_minute(numeric_system::standard); | |||
| break; | |||
| case 'S': | |||
| handler.on_second(numeric_system::standard); | |||
| 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(); | |||
| 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 '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; | |||
| default: | |||
| FMT_THROW(format_error("invalid format")); | |||
| } | |||
| break; | |||
| } | |||
| case 'O': | |||
| if (ptr == end) FMT_THROW(format_error("invalid format")); | |||
| c = *ptr++; | |||
| switch (c) { | |||
| 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); | |||
| break; | |||
| case 'I': | |||
| handler.on_12_hour(numeric_system::alternative); | |||
| break; | |||
| case 'M': | |||
| handler.on_minute(numeric_system::alternative); | |||
| break; | |||
| case 'S': | |||
| handler.on_second(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; | |||
| } | |||
| struct chrono_format_checker { | |||
| FMT_NORETURN void report_no_date() { FMT_THROW(format_error("no date")); } | |||
| template <typename Char> void on_text(const Char*, const Char*) {} | |||
| FMT_NORETURN void on_abbr_weekday() { report_no_date(); } | |||
| FMT_NORETURN void on_full_weekday() { report_no_date(); } | |||
| FMT_NORETURN void on_dec0_weekday(numeric_system) { report_no_date(); } | |||
| FMT_NORETURN void on_dec1_weekday(numeric_system) { report_no_date(); } | |||
| FMT_NORETURN void on_abbr_month() { report_no_date(); } | |||
| FMT_NORETURN void on_full_month() { report_no_date(); } | |||
| void on_24_hour(numeric_system) {} | |||
| void on_12_hour(numeric_system) {} | |||
| void on_minute(numeric_system) {} | |||
| void on_second(numeric_system) {} | |||
| FMT_NORETURN void on_datetime(numeric_system) { report_no_date(); } | |||
| FMT_NORETURN void on_loc_date(numeric_system) { report_no_date(); } | |||
| FMT_NORETURN void on_loc_time(numeric_system) { report_no_date(); } | |||
| FMT_NORETURN void on_us_date() { report_no_date(); } | |||
| FMT_NORETURN void on_iso_date() { report_no_date(); } | |||
| void on_12_hour_time() {} | |||
| void on_24_hour_time() {} | |||
| void on_iso_time() {} | |||
| void on_am_pm() {} | |||
| void on_duration_value() {} | |||
| void on_duration_unit() {} | |||
| FMT_NORETURN void on_utc_offset() { report_no_date(); } | |||
| FMT_NORETURN void on_tz_name() { report_no_date(); } | |||
| }; | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| inline bool isnan(T) { | |||
| return false; | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||
| inline bool isnan(T value) { | |||
| return std::isnan(value); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| inline bool isfinite(T) { | |||
| return true; | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||
| inline bool isfinite(T value) { | |||
| return std::isfinite(value); | |||
| } | |||
| // Convers value to int and checks that it's in the range [0, upper). | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| inline int to_nonnegative_int(T value, int upper) { | |||
| FMT_ASSERT(value >= 0 && value <= upper, "invalid value"); | |||
| (void)upper; | |||
| return static_cast<int>(value); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||
| inline int to_nonnegative_int(T value, int upper) { | |||
| FMT_ASSERT( | |||
| std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)), | |||
| "invalid value"); | |||
| (void)upper; | |||
| return static_cast<int>(value); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| inline T mod(T x, int y) { | |||
| return x % y; | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||
| inline T mod(T x, int y) { | |||
| return std::fmod(x, static_cast<T>(y)); | |||
| } | |||
| // If T is an integral type, maps T to its unsigned counterpart, otherwise | |||
| // leaves it unchanged (unlike std::make_unsigned). | |||
| template <typename T, bool INTEGRAL = std::is_integral<T>::value> | |||
| struct make_unsigned_or_unchanged { | |||
| using type = T; | |||
| }; | |||
| template <typename T> struct make_unsigned_or_unchanged<T, true> { | |||
| using type = typename std::make_unsigned<T>::type; | |||
| }; | |||
| #if FMT_SAFE_DURATION_CAST | |||
| // throwing version of safe_duration_cast | |||
| template <typename To, typename FromRep, typename FromPeriod> | |||
| To fmt_safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from) { | |||
| int ec; | |||
| To to = safe_duration_cast::safe_duration_cast<To>(from, ec); | |||
| if (ec) FMT_THROW(format_error("cannot format duration")); | |||
| return to; | |||
| } | |||
| #endif | |||
| template <typename Rep, typename Period, | |||
| FMT_ENABLE_IF(std::is_integral<Rep>::value)> | |||
| inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||
| std::chrono::duration<Rep, Period> d) { | |||
| // this may overflow and/or the result may not fit in the | |||
| // target type. | |||
| #if FMT_SAFE_DURATION_CAST | |||
| using CommonSecondsType = | |||
| typename std::common_type<decltype(d), std::chrono::seconds>::type; | |||
| const auto d_as_common = fmt_safe_duration_cast<CommonSecondsType>(d); | |||
| const auto d_as_whole_seconds = | |||
| fmt_safe_duration_cast<std::chrono::seconds>(d_as_common); | |||
| // this conversion should be nonproblematic | |||
| const auto diff = d_as_common - d_as_whole_seconds; | |||
| const auto ms = | |||
| fmt_safe_duration_cast<std::chrono::duration<Rep, std::milli>>(diff); | |||
| return ms; | |||
| #else | |||
| auto s = std::chrono::duration_cast<std::chrono::seconds>(d); | |||
| return std::chrono::duration_cast<std::chrono::milliseconds>(d - s); | |||
| #endif | |||
| } | |||
| template <typename Rep, typename Period, | |||
| FMT_ENABLE_IF(std::is_floating_point<Rep>::value)> | |||
| inline std::chrono::duration<Rep, std::milli> get_milliseconds( | |||
| std::chrono::duration<Rep, Period> d) { | |||
| using common_type = typename std::common_type<Rep, std::intmax_t>::type; | |||
| auto ms = mod(d.count() * static_cast<common_type>(Period::num) / | |||
| static_cast<common_type>(Period::den) * 1000, | |||
| 1000); | |||
| return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms)); | |||
| } | |||
| template <typename Rep, typename OutputIt> | |||
| OutputIt format_chrono_duration_value(OutputIt out, Rep val, int precision) { | |||
| if (precision >= 0) return format_to(out, "{:.{}f}", val, precision); | |||
| return format_to(out, std::is_floating_point<Rep>::value ? "{:g}" : "{}", | |||
| val); | |||
| } | |||
| template <typename Period, typename OutputIt> | |||
| static OutputIt format_chrono_duration_unit(OutputIt out) { | |||
| if (const char* unit = get_units<Period>()) return format_to(out, "{}", unit); | |||
| if (Period::den == 1) return format_to(out, "[{}]s", Period::num); | |||
| return format_to(out, "[{}/{}]s", Period::num, Period::den); | |||
| } | |||
| template <typename FormatContext, typename OutputIt, typename Rep, | |||
| typename Period> | |||
| struct chrono_formatter { | |||
| FormatContext& context; | |||
| OutputIt out; | |||
| int precision; | |||
| // rep is unsigned to avoid overflow. | |||
| using rep = | |||
| conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), | |||
| unsigned, typename make_unsigned_or_unchanged<Rep>::type>; | |||
| rep val; | |||
| using seconds = std::chrono::duration<rep>; | |||
| seconds s; | |||
| using milliseconds = std::chrono::duration<rep, std::milli>; | |||
| bool negative; | |||
| using char_type = typename FormatContext::char_type; | |||
| explicit chrono_formatter(FormatContext& ctx, OutputIt o, | |||
| std::chrono::duration<Rep, Period> d) | |||
| : context(ctx), out(o), val(d.count()), 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. | |||
| #if FMT_SAFE_DURATION_CAST | |||
| // might need checked conversion (rep!=Rep) | |||
| auto tmpval = std::chrono::duration<rep, Period>(val); | |||
| s = fmt_safe_duration_cast<seconds>(tmpval); | |||
| #else | |||
| s = std::chrono::duration_cast<seconds>( | |||
| std::chrono::duration<rep, Period>(val)); | |||
| #endif | |||
| } | |||
| // returns true if nan or inf, writes to out. | |||
| bool handle_nan_inf() { | |||
| if (isfinite(val)) { | |||
| return false; | |||
| } | |||
| if (isnan(val)) { | |||
| write_nan(); | |||
| return true; | |||
| } | |||
| // must be +-inf | |||
| if (val > 0) { | |||
| write_pinf(); | |||
| } else { | |||
| write_ninf(); | |||
| } | |||
| return true; | |||
| } | |||
| Rep hour() const { return static_cast<Rep>(mod((s.count() / 3600), 24)); } | |||
| Rep hour12() const { | |||
| Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12)); | |||
| return hour <= 0 ? 12 : hour; | |||
| } | |||
| Rep minute() const { return static_cast<Rep>(mod((s.count() / 60), 60)); } | |||
| Rep second() const { return static_cast<Rep>(mod(s.count(), 60)); } | |||
| std::tm time() const { | |||
| 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) { | |||
| *out++ = '-'; | |||
| negative = false; | |||
| } | |||
| } | |||
| void write(Rep value, int width) { | |||
| write_sign(); | |||
| if (isnan(value)) return write_nan(); | |||
| uint32_or_64_t<int> n = to_unsigned( | |||
| to_nonnegative_int(value, (std::numeric_limits<int>::max)())); | |||
| int num_digits = internal::count_digits(n); | |||
| if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); | |||
| out = format_decimal<char_type>(out, n, num_digits); | |||
| } | |||
| void write_nan() { std::copy_n("nan", 3, out); } | |||
| void write_pinf() { std::copy_n("inf", 3, out); } | |||
| void write_ninf() { std::copy_n("-inf", 4, out); } | |||
| void format_localized(const tm& time, const char* format) { | |||
| if (isnan(val)) return write_nan(); | |||
| auto locale = context.locale().template get<std::locale>(); | |||
| auto& facet = std::use_facet<std::time_put<char_type>>(locale); | |||
| std::basic_ostringstream<char_type> os; | |||
| os.imbue(locale); | |||
| facet.put(os, os, ' ', &time, format, format + std::strlen(format)); | |||
| auto str = os.str(); | |||
| std::copy(str.begin(), str.end(), out); | |||
| } | |||
| void on_text(const char_type* begin, const char_type* end) { | |||
| std::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() {} | |||
| void on_tz_name() {} | |||
| void on_24_hour(numeric_system ns) { | |||
| if (handle_nan_inf()) return; | |||
| if (ns == numeric_system::standard) return write(hour(), 2); | |||
| auto time = tm(); | |||
| time.tm_hour = to_nonnegative_int(hour(), 24); | |||
| format_localized(time, "%OH"); | |||
| } | |||
| void on_12_hour(numeric_system ns) { | |||
| if (handle_nan_inf()) return; | |||
| if (ns == numeric_system::standard) return write(hour12(), 2); | |||
| auto time = tm(); | |||
| time.tm_hour = to_nonnegative_int(hour12(), 12); | |||
| format_localized(time, "%OI"); | |||
| } | |||
| void on_minute(numeric_system ns) { | |||
| if (handle_nan_inf()) return; | |||
| if (ns == numeric_system::standard) return write(minute(), 2); | |||
| auto time = tm(); | |||
| time.tm_min = to_nonnegative_int(minute(), 60); | |||
| format_localized(time, "%OM"); | |||
| } | |||
| void on_second(numeric_system ns) { | |||
| if (handle_nan_inf()) return; | |||
| if (ns == numeric_system::standard) { | |||
| write(second(), 2); | |||
| #if FMT_SAFE_DURATION_CAST | |||
| // convert rep->Rep | |||
| using duration_rep = std::chrono::duration<rep, Period>; | |||
| using duration_Rep = std::chrono::duration<Rep, Period>; | |||
| auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val}); | |||
| #else | |||
| auto tmpval = std::chrono::duration<Rep, Period>(val); | |||
| #endif | |||
| auto ms = get_milliseconds(tmpval); | |||
| if (ms != std::chrono::milliseconds(0)) { | |||
| *out++ = '.'; | |||
| write(ms.count(), 3); | |||
| } | |||
| return; | |||
| } | |||
| auto time = tm(); | |||
| time.tm_sec = to_nonnegative_int(second(), 60); | |||
| format_localized(time, "%OS"); | |||
| } | |||
| void on_12_hour_time() { | |||
| if (handle_nan_inf()) return; | |||
| format_localized(time(), "%r"); | |||
| } | |||
| 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; | |||
| write(second(), 2); | |||
| } | |||
| void on_am_pm() { | |||
| if (handle_nan_inf()) return; | |||
| format_localized(time(), "%p"); | |||
| } | |||
| void on_duration_value() { | |||
| if (handle_nan_inf()) return; | |||
| write_sign(); | |||
| out = format_chrono_duration_value(out, val, precision); | |||
| } | |||
| void on_duration_unit() { out = format_chrono_duration_unit<Period>(out); } | |||
| }; | |||
| } // namespace internal | |||
| template <typename Rep, typename Period, typename Char> | |||
| struct formatter<std::chrono::duration<Rep, Period>, Char> { | |||
| private: | |||
| basic_format_specs<Char> specs; | |||
| int precision; | |||
| using arg_ref_type = internal::arg_ref<Char>; | |||
| arg_ref_type width_ref; | |||
| arg_ref_type precision_ref; | |||
| mutable basic_string_view<Char> format_str; | |||
| using duration = std::chrono::duration<Rep, Period>; | |||
| struct spec_handler { | |||
| formatter& f; | |||
| basic_parse_context<Char>& context; | |||
| basic_string_view<Char> format_str; | |||
| template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { | |||
| context.check_arg_id(arg_id); | |||
| return arg_ref_type(arg_id); | |||
| } | |||
| FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) { | |||
| context.check_arg_id(arg_id); | |||
| const auto str_val = internal::string_view_metadata(format_str, arg_id); | |||
| return arg_ref_type(str_val); | |||
| } | |||
| FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { | |||
| return arg_ref_type(context.next_arg_id()); | |||
| } | |||
| void on_error(const char* msg) { FMT_THROW(format_error(msg)); } | |||
| void on_fill(Char fill) { f.specs.fill[0] = fill; } | |||
| void on_align(align_t align) { f.specs.align = align; } | |||
| void on_width(unsigned width) { f.specs.width = width; } | |||
| void on_precision(unsigned precision) { f.precision = precision; } | |||
| void end_precision() {} | |||
| template <typename Id> void on_dynamic_width(Id arg_id) { | |||
| f.width_ref = make_arg_ref(arg_id); | |||
| } | |||
| template <typename Id> void on_dynamic_precision(Id arg_id) { | |||
| f.precision_ref = make_arg_ref(arg_id); | |||
| } | |||
| }; | |||
| using iterator = typename basic_parse_context<Char>::iterator; | |||
| struct parse_range { | |||
| iterator begin; | |||
| iterator end; | |||
| }; | |||
| FMT_CONSTEXPR parse_range do_parse(basic_parse_context<Char>& ctx) { | |||
| auto begin = ctx.begin(), end = ctx.end(); | |||
| if (begin == end || *begin == '}') return {begin, begin}; | |||
| spec_handler handler{*this, ctx, format_str}; | |||
| begin = internal::parse_align(begin, end, handler); | |||
| if (begin == end) return {begin, begin}; | |||
| begin = internal::parse_width(begin, end, handler); | |||
| if (begin == end) return {begin, begin}; | |||
| if (*begin == '.') { | |||
| if (std::is_floating_point<Rep>::value) | |||
| begin = internal::parse_precision(begin, end, handler); | |||
| else | |||
| handler.on_error("precision not allowed for this argument type"); | |||
| } | |||
| end = parse_chrono_format(begin, end, internal::chrono_format_checker()); | |||
| return {begin, end}; | |||
| } | |||
| public: | |||
| formatter() : precision(-1) {} | |||
| FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx) | |||
| -> decltype(ctx.begin()) { | |||
| auto range = do_parse(ctx); | |||
| format_str = basic_string_view<Char>( | |||
| &*range.begin, internal::to_unsigned(range.end - range.begin)); | |||
| return range.end; | |||
| } | |||
| template <typename FormatContext> | |||
| auto format(const duration& d, FormatContext& ctx) -> decltype(ctx.out()) { | |||
| auto begin = format_str.begin(), end = format_str.end(); | |||
| // As a possible future optimization, we could avoid extra copying if width | |||
| // is not specified. | |||
| basic_memory_buffer<Char> buf; | |||
| auto out = std::back_inserter(buf); | |||
| using range = internal::output_range<decltype(ctx.out()), Char>; | |||
| internal::basic_writer<range> w(range(ctx.out())); | |||
| internal::handle_dynamic_spec<internal::width_checker>( | |||
| specs.width, width_ref, ctx, format_str.begin()); | |||
| internal::handle_dynamic_spec<internal::precision_checker>( | |||
| precision, precision_ref, ctx, format_str.begin()); | |||
| if (begin == end || *begin == '}') { | |||
| out = internal::format_chrono_duration_value(out, d.count(), precision); | |||
| internal::format_chrono_duration_unit<Period>(out); | |||
| } else { | |||
| internal::chrono_formatter<FormatContext, decltype(out), Rep, Period> f( | |||
| ctx, out, d); | |||
| f.precision = precision; | |||
| parse_chrono_format(begin, end, f); | |||
| } | |||
| w.write(buf.data(), buf.size(), specs); | |||
| return w.out(); | |||
| } | |||
| }; | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_CHRONO_H_ | |||
| @@ -0,0 +1,585 @@ | |||
| // 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 | |||
| 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, | |||
| italic = 1 << 1, | |||
| underline = 1 << 2, | |||
| strikethrough = 1 << 3 | |||
| }; | |||
| // 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 { | |||
| FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} | |||
| FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} | |||
| FMT_CONSTEXPR rgb(uint32_t hex) | |||
| : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} | |||
| FMT_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 internal { | |||
| // color is a struct of either a rgb color or a terminal color. | |||
| struct color_type { | |||
| FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {} | |||
| FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true), | |||
| value{} { | |||
| value.rgb_color = static_cast<uint32_t>(rgb_color); | |||
| } | |||
| FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} { | |||
| value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) | | |||
| (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b; | |||
| } | |||
| FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(), | |||
| value{} { | |||
| value.term_color = static_cast<uint8_t>(term_color); | |||
| } | |||
| bool is_rgb; | |||
| union color_union { | |||
| uint8_t term_color; | |||
| uint32_t rgb_color; | |||
| } value; | |||
| }; | |||
| } // namespace internal | |||
| // Experimental text formatting support. | |||
| class text_style { | |||
| public: | |||
| FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT | |||
| : set_foreground_color(), | |||
| set_background_color(), | |||
| ems(em) {} | |||
| FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { | |||
| if (!set_foreground_color) { | |||
| set_foreground_color = rhs.set_foreground_color; | |||
| foreground_color = rhs.foreground_color; | |||
| } else if (rhs.set_foreground_color) { | |||
| if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | |||
| FMT_THROW(format_error("can't OR a terminal color")); | |||
| foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; | |||
| } | |||
| if (!set_background_color) { | |||
| set_background_color = rhs.set_background_color; | |||
| background_color = rhs.background_color; | |||
| } else if (rhs.set_background_color) { | |||
| if (!background_color.is_rgb || !rhs.background_color.is_rgb) | |||
| FMT_THROW(format_error("can't OR a terminal color")); | |||
| background_color.value.rgb_color |= rhs.background_color.value.rgb_color; | |||
| } | |||
| ems = static_cast<emphasis>(static_cast<uint8_t>(ems) | | |||
| static_cast<uint8_t>(rhs.ems)); | |||
| return *this; | |||
| } | |||
| friend FMT_CONSTEXPR text_style operator|(text_style lhs, | |||
| const text_style& rhs) { | |||
| return lhs |= rhs; | |||
| } | |||
| FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) { | |||
| if (!set_foreground_color) { | |||
| set_foreground_color = rhs.set_foreground_color; | |||
| foreground_color = rhs.foreground_color; | |||
| } else if (rhs.set_foreground_color) { | |||
| if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) | |||
| FMT_THROW(format_error("can't AND a terminal color")); | |||
| foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color; | |||
| } | |||
| if (!set_background_color) { | |||
| set_background_color = rhs.set_background_color; | |||
| background_color = rhs.background_color; | |||
| } else if (rhs.set_background_color) { | |||
| if (!background_color.is_rgb || !rhs.background_color.is_rgb) | |||
| FMT_THROW(format_error("can't AND a terminal color")); | |||
| background_color.value.rgb_color &= rhs.background_color.value.rgb_color; | |||
| } | |||
| ems = static_cast<emphasis>(static_cast<uint8_t>(ems) & | |||
| static_cast<uint8_t>(rhs.ems)); | |||
| return *this; | |||
| } | |||
| friend FMT_CONSTEXPR text_style operator&(text_style lhs, | |||
| const text_style& rhs) { | |||
| return lhs &= rhs; | |||
| } | |||
| FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT { | |||
| return set_foreground_color; | |||
| } | |||
| FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT { | |||
| return set_background_color; | |||
| } | |||
| FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { | |||
| return static_cast<uint8_t>(ems) != 0; | |||
| } | |||
| FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT { | |||
| assert(has_foreground() && "no foreground specified for this style"); | |||
| return foreground_color; | |||
| } | |||
| FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT { | |||
| assert(has_background() && "no background specified for this style"); | |||
| return background_color; | |||
| } | |||
| FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT { | |||
| assert(has_emphasis() && "no emphasis specified for this style"); | |||
| return ems; | |||
| } | |||
| private: | |||
| FMT_CONSTEXPR text_style(bool is_foreground, | |||
| internal::color_type text_color) FMT_NOEXCEPT | |||
| : set_foreground_color(), | |||
| set_background_color(), | |||
| ems() { | |||
| if (is_foreground) { | |||
| foreground_color = text_color; | |||
| set_foreground_color = true; | |||
| } else { | |||
| background_color = text_color; | |||
| set_background_color = true; | |||
| } | |||
| } | |||
| friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground) | |||
| FMT_NOEXCEPT; | |||
| friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background) | |||
| FMT_NOEXCEPT; | |||
| internal::color_type foreground_color; | |||
| internal::color_type background_color; | |||
| bool set_foreground_color; | |||
| bool set_background_color; | |||
| emphasis ems; | |||
| }; | |||
| FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT { | |||
| return text_style(/*is_foreground=*/true, foreground); | |||
| } | |||
| FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT { | |||
| return text_style(/*is_foreground=*/false, background); | |||
| } | |||
| FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { | |||
| return text_style(lhs) | rhs; | |||
| } | |||
| namespace internal { | |||
| template <typename Char> struct ansi_color_escape { | |||
| FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, | |||
| const char* esc) FMT_NOEXCEPT { | |||
| // If we have a terminal color, we need to output another escape code | |||
| // sequence. | |||
| if (!text_color.is_rgb) { | |||
| bool is_background = esc == internal::data::background_color; | |||
| uint32_t value = text_color.value.term_color; | |||
| // Background ASCII codes are the same as the foreground ones but with | |||
| // 10 more. | |||
| if (is_background) value += 10u; | |||
| std::size_t index = 0; | |||
| buffer[index++] = static_cast<Char>('\x1b'); | |||
| buffer[index++] = static_cast<Char>('['); | |||
| if (value >= 100u) { | |||
| buffer[index++] = static_cast<Char>('1'); | |||
| value %= 100u; | |||
| } | |||
| buffer[index++] = static_cast<Char>('0' + value / 10u); | |||
| buffer[index++] = static_cast<Char>('0' + value % 10u); | |||
| buffer[index++] = static_cast<Char>('m'); | |||
| buffer[index++] = static_cast<Char>('\0'); | |||
| return; | |||
| } | |||
| for (int i = 0; i < 7; i++) { | |||
| buffer[i] = static_cast<Char>(esc[i]); | |||
| } | |||
| rgb color(text_color.value.rgb_color); | |||
| to_esc(color.r, buffer + 7, ';'); | |||
| to_esc(color.g, buffer + 11, ';'); | |||
| to_esc(color.b, buffer + 15, 'm'); | |||
| buffer[19] = static_cast<Char>(0); | |||
| } | |||
| FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { | |||
| uint8_t em_codes[4] = {}; | |||
| uint8_t em_bits = static_cast<uint8_t>(em); | |||
| if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; | |||
| if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; | |||
| if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; | |||
| if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) | |||
| em_codes[3] = 9; | |||
| std::size_t index = 0; | |||
| for (int i = 0; i < 4; ++i) { | |||
| if (!em_codes[i]) continue; | |||
| buffer[index++] = static_cast<Char>('\x1b'); | |||
| buffer[index++] = static_cast<Char>('['); | |||
| buffer[index++] = static_cast<Char>('0' + em_codes[i]); | |||
| buffer[index++] = static_cast<Char>('m'); | |||
| } | |||
| buffer[index++] = static_cast<Char>(0); | |||
| } | |||
| FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; } | |||
| FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } | |||
| FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { | |||
| return buffer + std::strlen(buffer); | |||
| } | |||
| private: | |||
| Char buffer[7u + 3u * 4u + 1u]; | |||
| static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, | |||
| char delimiter) FMT_NOEXCEPT { | |||
| out[0] = static_cast<Char>('0' + c / 100); | |||
| out[1] = static_cast<Char>('0' + c / 10 % 10); | |||
| out[2] = static_cast<Char>('0' + c % 10); | |||
| out[3] = static_cast<Char>(delimiter); | |||
| } | |||
| }; | |||
| template <typename Char> | |||
| FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color( | |||
| internal::color_type foreground) FMT_NOEXCEPT { | |||
| return ansi_color_escape<Char>(foreground, internal::data::foreground_color); | |||
| } | |||
| template <typename Char> | |||
| FMT_CONSTEXPR ansi_color_escape<Char> make_background_color( | |||
| internal::color_type background) FMT_NOEXCEPT { | |||
| return ansi_color_escape<Char>(background, internal::data::background_color); | |||
| } | |||
| template <typename Char> | |||
| FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT { | |||
| return ansi_color_escape<Char>(em); | |||
| } | |||
| template <typename Char> | |||
| inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT { | |||
| std::fputs(chars, stream); | |||
| } | |||
| template <> | |||
| inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { | |||
| std::fputws(chars, stream); | |||
| } | |||
| template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT { | |||
| fputs(internal::data::reset_color, stream); | |||
| } | |||
| template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT { | |||
| fputs(internal::data::wreset_color, stream); | |||
| } | |||
| template <typename Char> | |||
| inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT { | |||
| const char* begin = data::reset_color; | |||
| const char* end = begin + sizeof(data::reset_color) - 1; | |||
| buffer.append(begin, end); | |||
| } | |||
| template <typename Char> | |||
| std::basic_string<Char> vformat(const text_style& ts, | |||
| basic_string_view<Char> format_str, | |||
| basic_format_args<buffer_context<Char> > args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| bool has_style = false; | |||
| if (ts.has_emphasis()) { | |||
| has_style = true; | |||
| ansi_color_escape<Char> escape = make_emphasis<Char>(ts.get_emphasis()); | |||
| buffer.append(escape.begin(), escape.end()); | |||
| } | |||
| if (ts.has_foreground()) { | |||
| has_style = true; | |||
| ansi_color_escape<Char> escape = | |||
| make_foreground_color<Char>(ts.get_foreground()); | |||
| buffer.append(escape.begin(), escape.end()); | |||
| } | |||
| if (ts.has_background()) { | |||
| has_style = true; | |||
| ansi_color_escape<Char> escape = | |||
| make_background_color<Char>(ts.get_background()); | |||
| buffer.append(escape.begin(), escape.end()); | |||
| } | |||
| internal::vformat_to(buffer, format_str, args); | |||
| if (has_style) { | |||
| reset_color<Char>(buffer); | |||
| } | |||
| return fmt::to_string(buffer); | |||
| } | |||
| } // namespace internal | |||
| template <typename S, typename Char = char_t<S> > | |||
| void vprint(std::FILE* f, const text_style& ts, const S& format, | |||
| basic_format_args<buffer_context<Char> > args) { | |||
| bool has_style = false; | |||
| if (ts.has_emphasis()) { | |||
| has_style = true; | |||
| internal::fputs<Char>(internal::make_emphasis<Char>(ts.get_emphasis()), f); | |||
| } | |||
| if (ts.has_foreground()) { | |||
| has_style = true; | |||
| internal::fputs<Char>( | |||
| internal::make_foreground_color<Char>(ts.get_foreground()), f); | |||
| } | |||
| if (ts.has_background()) { | |||
| has_style = true; | |||
| internal::fputs<Char>( | |||
| internal::make_background_color<Char>(ts.get_background()), f); | |||
| } | |||
| vprint(f, format, args); | |||
| if (has_style) { | |||
| internal::reset_color<Char>(f); | |||
| } | |||
| } | |||
| /** | |||
| 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 <typename S, typename... Args, | |||
| FMT_ENABLE_IF(internal::is_string<S>::value)> | |||
| void print(std::FILE* f, const text_style& ts, const S& format_str, | |||
| const Args&... args) { | |||
| internal::check_format_string<Args...>(format_str); | |||
| using context = buffer_context<char_t<S> >; | |||
| format_arg_store<context, Args...> as{args...}; | |||
| vprint(f, ts, format_str, basic_format_args<context>(as)); | |||
| } | |||
| /** | |||
| 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 <typename S, typename... Args, | |||
| FMT_ENABLE_IF(internal::is_string<S>::value)> | |||
| void print(const text_style& ts, const S& format_str, const Args&... args) { | |||
| return print(stdout, ts, format_str, args...); | |||
| } | |||
| template <typename S, typename Char = char_t<S> > | |||
| inline std::basic_string<Char> vformat( | |||
| const text_style& ts, const S& format_str, | |||
| basic_format_args<buffer_context<Char> > args) { | |||
| return internal::vformat(ts, to_string_view(format_str), args); | |||
| } | |||
| /** | |||
| \rst | |||
| Formats arguments and returns the result as a string using ANSI | |||
| escape sequences to specify text formatting. | |||
| **Example**:: | |||
| #include <fmt/color.h> | |||
| std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), | |||
| "The answer is {}", 42); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, typename Char = char_t<S> > | |||
| inline std::basic_string<Char> format(const text_style& ts, const S& format_str, | |||
| const Args&... args) { | |||
| return internal::vformat(ts, to_string_view(format_str), | |||
| {internal::make_args_checked(format_str, args...)}); | |||
| } | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_COLOR_H_ | |||
| @@ -0,0 +1,466 @@ | |||
| // 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_ | |||
| #include <vector> | |||
| #include "format.h" | |||
| FMT_BEGIN_NAMESPACE | |||
| namespace internal { | |||
| template <typename Char> struct format_part { | |||
| public: | |||
| struct named_argument_id { | |||
| FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id) | |||
| : id(id) {} | |||
| internal::string_view_metadata id; | |||
| }; | |||
| struct argument_id { | |||
| FMT_CONSTEXPR argument_id() : argument_id(0u) {} | |||
| FMT_CONSTEXPR argument_id(unsigned id) | |||
| : which(which_arg_id::index), val(id) {} | |||
| FMT_CONSTEXPR argument_id(internal::string_view_metadata id) | |||
| : which(which_arg_id::named_index), val(id) {} | |||
| enum class which_arg_id { index, named_index }; | |||
| which_arg_id which; | |||
| union value { | |||
| FMT_CONSTEXPR value() : index(0u) {} | |||
| FMT_CONSTEXPR value(unsigned id) : index(id) {} | |||
| FMT_CONSTEXPR value(internal::string_view_metadata id) | |||
| : named_index(id) {} | |||
| unsigned index; | |||
| internal::string_view_metadata named_index; | |||
| } val; | |||
| }; | |||
| struct specification { | |||
| FMT_CONSTEXPR specification() : arg_id(0u) {} | |||
| FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {} | |||
| FMT_CONSTEXPR specification(internal::string_view_metadata id) | |||
| : arg_id(id) {} | |||
| argument_id arg_id; | |||
| internal::dynamic_format_specs<Char> parsed_specs; | |||
| }; | |||
| FMT_CONSTEXPR format_part() | |||
| : which(kind::argument_id), end_of_argument_id(0u), val(0u) {} | |||
| FMT_CONSTEXPR format_part(internal::string_view_metadata text) | |||
| : which(kind::text), end_of_argument_id(0u), val(text) {} | |||
| FMT_CONSTEXPR format_part(unsigned id) | |||
| : which(kind::argument_id), end_of_argument_id(0u), val(id) {} | |||
| FMT_CONSTEXPR format_part(named_argument_id arg_id) | |||
| : which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {} | |||
| FMT_CONSTEXPR format_part(specification spec) | |||
| : which(kind::specification), end_of_argument_id(0u), val(spec) {} | |||
| enum class kind { argument_id, named_argument_id, text, specification }; | |||
| kind which; | |||
| std::size_t end_of_argument_id; | |||
| union value { | |||
| FMT_CONSTEXPR value() : arg_id(0u) {} | |||
| FMT_CONSTEXPR value(unsigned id) : arg_id(id) {} | |||
| FMT_CONSTEXPR value(named_argument_id named_id) | |||
| : named_arg_id(named_id.id) {} | |||
| FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {} | |||
| FMT_CONSTEXPR value(specification s) : spec(s) {} | |||
| unsigned arg_id; | |||
| internal::string_view_metadata named_arg_id; | |||
| internal::string_view_metadata text; | |||
| specification spec; | |||
| } val; | |||
| }; | |||
| template <typename Char, typename PartsContainer> | |||
| class format_preparation_handler : public internal::error_handler { | |||
| private: | |||
| using part = format_part<Char>; | |||
| public: | |||
| using iterator = typename basic_string_view<Char>::iterator; | |||
| FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format, | |||
| PartsContainer& parts) | |||
| : parts_(parts), format_(format), parse_context_(format) {} | |||
| FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||
| if (begin == end) return; | |||
| const auto offset = begin - format_.data(); | |||
| const auto size = end - begin; | |||
| parts_.push_back(part(string_view_metadata(offset, size))); | |||
| } | |||
| FMT_CONSTEXPR void on_arg_id() { | |||
| parts_.push_back(part(parse_context_.next_arg_id())); | |||
| } | |||
| FMT_CONSTEXPR void on_arg_id(unsigned id) { | |||
| parse_context_.check_arg_id(id); | |||
| parts_.push_back(part(id)); | |||
| } | |||
| FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) { | |||
| const auto view = string_view_metadata(format_, id); | |||
| const auto arg_id = typename part::named_argument_id(view); | |||
| parts_.push_back(part(arg_id)); | |||
| } | |||
| FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { | |||
| parts_.back().end_of_argument_id = ptr - format_.begin(); | |||
| } | |||
| FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||
| const Char* end) { | |||
| const auto specs_offset = to_unsigned(begin - format_.begin()); | |||
| using parse_context = basic_parse_context<Char>; | |||
| internal::dynamic_format_specs<Char> parsed_specs; | |||
| dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_); | |||
| begin = parse_format_specs(begin, end, handler); | |||
| if (*begin != '}') on_error("missing '}' in format string"); | |||
| auto& last_part = parts_.back(); | |||
| auto specs = last_part.which == part::kind::argument_id | |||
| ? typename part::specification(last_part.val.arg_id) | |||
| : typename part::specification(last_part.val.named_arg_id); | |||
| specs.parsed_specs = parsed_specs; | |||
| last_part = part(specs); | |||
| last_part.end_of_argument_id = specs_offset; | |||
| return begin; | |||
| } | |||
| private: | |||
| PartsContainer& parts_; | |||
| basic_string_view<Char> format_; | |||
| basic_parse_context<Char> parse_context_; | |||
| }; | |||
| template <typename Format, typename PreparedPartsProvider, typename... Args> | |||
| class prepared_format { | |||
| public: | |||
| using char_type = char_t<Format>; | |||
| using format_part_t = format_part<char_type>; | |||
| constexpr prepared_format(Format f) | |||
| : format_(std::move(f)), parts_provider_(to_string_view(format_)) {} | |||
| prepared_format() = delete; | |||
| using context = buffer_context<char_type>; | |||
| template <typename Range, typename Context> | |||
| auto vformat_to(Range out, basic_format_args<Context> args) const -> | |||
| typename Context::iterator { | |||
| const auto format_view = internal::to_string_view(format_); | |||
| basic_parse_context<char_type> parse_ctx(format_view); | |||
| Context ctx(out.begin(), args); | |||
| const auto& parts = parts_provider_.parts(); | |||
| for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) { | |||
| const auto& part = *part_it; | |||
| const auto& value = part.val; | |||
| switch (part.which) { | |||
| case format_part_t::kind::text: { | |||
| const auto text = value.text.to_view(format_view.data()); | |||
| auto output = ctx.out(); | |||
| auto&& it = internal::reserve(output, text.size()); | |||
| it = std::copy_n(text.begin(), text.size(), it); | |||
| ctx.advance_to(output); | |||
| } break; | |||
| case format_part_t::kind::argument_id: { | |||
| advance_parse_context_to_specification(parse_ctx, part); | |||
| format_arg<Range>(parse_ctx, ctx, value.arg_id); | |||
| } break; | |||
| case format_part_t::kind::named_argument_id: { | |||
| advance_parse_context_to_specification(parse_ctx, part); | |||
| const auto named_arg_id = | |||
| value.named_arg_id.to_view(format_view.data()); | |||
| format_arg<Range>(parse_ctx, ctx, named_arg_id); | |||
| } break; | |||
| case format_part_t::kind::specification: { | |||
| const auto& arg_id_value = value.spec.arg_id.val; | |||
| const auto arg = value.spec.arg_id.which == | |||
| format_part_t::argument_id::which_arg_id::index | |||
| ? ctx.arg(arg_id_value.index) | |||
| : ctx.arg(arg_id_value.named_index.to_view( | |||
| to_string_view(format_).data())); | |||
| auto specs = value.spec.parsed_specs; | |||
| handle_dynamic_spec<internal::width_checker>( | |||
| specs.width, specs.width_ref, ctx, format_view.begin()); | |||
| handle_dynamic_spec<internal::precision_checker>( | |||
| specs.precision, specs.precision_ref, ctx, format_view.begin()); | |||
| check_prepared_specs(specs, arg.type()); | |||
| advance_parse_context_to_specification(parse_ctx, part); | |||
| ctx.advance_to( | |||
| visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg)); | |||
| } break; | |||
| } | |||
| } | |||
| return ctx.out(); | |||
| } | |||
| private: | |||
| void advance_parse_context_to_specification( | |||
| basic_parse_context<char_type>& parse_ctx, | |||
| const format_part_t& part) const { | |||
| const auto view = to_string_view(format_); | |||
| const auto specification_begin = view.data() + part.end_of_argument_id; | |||
| advance_to(parse_ctx, specification_begin); | |||
| } | |||
| template <typename Range, typename Context, typename Id> | |||
| void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx, | |||
| Id arg_id) const { | |||
| parse_ctx.check_arg_id(arg_id); | |||
| const auto stopped_at = | |||
| visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id)); | |||
| ctx.advance_to(stopped_at); | |||
| } | |||
| template <typename Char> | |||
| void check_prepared_specs(const basic_format_specs<Char>& specs, | |||
| internal::type arg_type) const { | |||
| internal::error_handler h; | |||
| numeric_specs_checker<internal::error_handler> checker(h, arg_type); | |||
| if (specs.align == align::numeric) checker.require_numeric_argument(); | |||
| if (specs.sign != sign::none) checker.check_sign(); | |||
| if (specs.alt) checker.require_numeric_argument(); | |||
| if (specs.precision >= 0) checker.check_precision(); | |||
| } | |||
| private: | |||
| Format format_; | |||
| PreparedPartsProvider parts_provider_; | |||
| }; | |||
| template <typename Char> struct part_counter { | |||
| unsigned num_parts = 0; | |||
| FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { | |||
| if (begin != end) ++num_parts; | |||
| } | |||
| FMT_CONSTEXPR void on_arg_id() { ++num_parts; } | |||
| FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } | |||
| FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) { ++num_parts; } | |||
| FMT_CONSTEXPR void on_replacement_field(const Char*) {} | |||
| FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, | |||
| const Char* end) { | |||
| // Find the matching brace. | |||
| unsigned braces_counter = 0; | |||
| for (; begin != end; ++begin) { | |||
| if (*begin == '{') { | |||
| ++braces_counter; | |||
| } else if (*begin == '}') { | |||
| if (braces_counter == 0u) break; | |||
| --braces_counter; | |||
| } | |||
| } | |||
| return begin; | |||
| } | |||
| FMT_CONSTEXPR void on_error(const char*) {} | |||
| }; | |||
| template <typename Format> class compiletime_prepared_parts_type_provider { | |||
| private: | |||
| using char_type = char_t<Format>; | |||
| static FMT_CONSTEXPR unsigned count_parts() { | |||
| FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{}); | |||
| part_counter<char_type> counter; | |||
| internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter); | |||
| return counter.num_parts; | |||
| } | |||
| // Workaround for old compilers. Compiletime parts preparation will not be | |||
| // performed with them anyway. | |||
| #if FMT_USE_CONSTEXPR | |||
| static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts = | |||
| compiletime_prepared_parts_type_provider::count_parts(); | |||
| #else | |||
| static const unsigned number_of_format_parts = 0u; | |||
| #endif | |||
| public: | |||
| template <unsigned N> struct format_parts_array { | |||
| using value_type = format_part<char_type>; | |||
| FMT_CONSTEXPR format_parts_array() : arr{} {} | |||
| FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; } | |||
| FMT_CONSTEXPR const value_type* begin() const { return arr; } | |||
| FMT_CONSTEXPR const value_type* end() const { return begin() + N; } | |||
| private: | |||
| value_type arr[N]; | |||
| }; | |||
| struct empty { | |||
| // Parts preparator will search for it | |||
| using value_type = format_part<char_type>; | |||
| }; | |||
| using type = conditional_t<number_of_format_parts != 0, | |||
| format_parts_array<number_of_format_parts>, empty>; | |||
| }; | |||
| template <typename Parts> class compiletime_prepared_parts_collector { | |||
| private: | |||
| using format_part = typename Parts::value_type; | |||
| public: | |||
| FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts) | |||
| : parts_{parts}, counter_{0u} {} | |||
| FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; } | |||
| FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; } | |||
| private: | |||
| Parts& parts_; | |||
| unsigned counter_; | |||
| }; | |||
| template <typename PartsContainer, typename Char> | |||
| FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) { | |||
| PartsContainer parts; | |||
| internal::parse_format_string</*IS_CONSTEXPR=*/false>( | |||
| format, format_preparation_handler<Char, PartsContainer>(format, parts)); | |||
| return parts; | |||
| } | |||
| template <typename PartsContainer, typename Char> | |||
| FMT_CONSTEXPR PartsContainer | |||
| prepare_compiletime_parts(basic_string_view<Char> format) { | |||
| using collector = compiletime_prepared_parts_collector<PartsContainer>; | |||
| PartsContainer parts; | |||
| collector c(parts); | |||
| internal::parse_format_string</*IS_CONSTEXPR=*/true>( | |||
| format, format_preparation_handler<Char, collector>(format, c)); | |||
| return parts; | |||
| } | |||
| template <typename PartsContainer> class runtime_parts_provider { | |||
| public: | |||
| runtime_parts_provider() = delete; | |||
| template <typename Char> | |||
| runtime_parts_provider(basic_string_view<Char> format) | |||
| : parts_(prepare_parts<PartsContainer>(format)) {} | |||
| const PartsContainer& parts() const { return parts_; } | |||
| private: | |||
| PartsContainer parts_; | |||
| }; | |||
| template <typename Format, typename PartsContainer> | |||
| struct compiletime_parts_provider { | |||
| compiletime_parts_provider() = delete; | |||
| template <typename Char> | |||
| FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {} | |||
| const PartsContainer& parts() const { | |||
| static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts = | |||
| prepare_compiletime_parts<PartsContainer>( | |||
| internal::to_string_view(Format{})); | |||
| return prepared_parts; | |||
| } | |||
| }; | |||
| } // namespace internal | |||
| #if FMT_USE_CONSTEXPR | |||
| template <typename... Args, typename S, | |||
| FMT_ENABLE_IF(is_compile_string<S>::value)> | |||
| FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format< | |||
| S, | |||
| internal::compiletime_parts_provider< | |||
| S, | |||
| typename internal::compiletime_prepared_parts_type_provider<S>::type>, | |||
| Args...> { | |||
| return format_str; | |||
| } | |||
| #endif | |||
| template <typename... Args, typename Char, size_t N> | |||
| auto compile(const Char (&format_str)[N]) -> internal::prepared_format< | |||
| std::basic_string<Char>, | |||
| internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>, | |||
| Args...> { | |||
| return std::basic_string<Char>(format_str, N - 1); | |||
| } | |||
| template <typename CompiledFormat, typename... Args, | |||
| typename Char = typename CompiledFormat::char_type> | |||
| std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| using range = internal::buffer_range<Char>; | |||
| using context = buffer_context<Char>; | |||
| cf.template vformat_to<range, context>(range(buffer), | |||
| {make_format_args<context>(args...)}); | |||
| return to_string(buffer); | |||
| } | |||
| template <typename OutputIt, typename CompiledFormat, typename... Args> | |||
| OutputIt format_to(OutputIt out, const CompiledFormat& cf, | |||
| const Args&... args) { | |||
| using char_type = typename CompiledFormat::char_type; | |||
| using range = internal::output_range<OutputIt, char_type>; | |||
| using context = format_context_t<OutputIt, char_type>; | |||
| return cf.template vformat_to<range, context>( | |||
| range(out), {make_format_args<context>(args...)}); | |||
| } | |||
| template <typename OutputIt, typename CompiledFormat, typename... Args, | |||
| FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value)> | |||
| format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, | |||
| const CompiledFormat& cf, | |||
| const Args&... args) { | |||
| auto it = | |||
| format_to(internal::truncating_iterator<OutputIt>(out, n), cf, args...); | |||
| return {it.base(), it.count()}; | |||
| } | |||
| template <typename CompiledFormat, typename... Args> | |||
| std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { | |||
| return fmt::format_to( | |||
| internal::counting_iterator<typename CompiledFormat::char_type>(), | |||
| cf, args...) | |||
| .count(); | |||
| } | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_COMPILE_H_ | |||
| @@ -0,0 +1,77 @@ | |||
| // Formatting library for C++ - std::locale support | |||
| // | |||
| // Copyright (c) 2012 - present, Victor Zverovich | |||
| // All rights reserved. | |||
| // | |||
| // For the license information refer to format.h. | |||
| #ifndef FMT_LOCALE_H_ | |||
| #define FMT_LOCALE_H_ | |||
| #include <locale> | |||
| #include "format.h" | |||
| FMT_BEGIN_NAMESPACE | |||
| namespace internal { | |||
| template <typename Char> | |||
| typename buffer_context<Char>::iterator vformat_to( | |||
| const std::locale& loc, buffer<Char>& buf, | |||
| basic_string_view<Char> format_str, | |||
| basic_format_args<buffer_context<Char>> args) { | |||
| using range = buffer_range<Char>; | |||
| return vformat_to<arg_formatter<range>>(buf, to_string_view(format_str), args, | |||
| internal::locale_ref(loc)); | |||
| } | |||
| template <typename Char> | |||
| std::basic_string<Char> vformat(const std::locale& loc, | |||
| basic_string_view<Char> format_str, | |||
| basic_format_args<buffer_context<Char>> args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| internal::vformat_to(loc, buffer, format_str, args); | |||
| return fmt::to_string(buffer); | |||
| } | |||
| } // namespace internal | |||
| template <typename S, typename Char = char_t<S>> | |||
| inline std::basic_string<Char> vformat( | |||
| const std::locale& loc, const S& format_str, | |||
| basic_format_args<buffer_context<Char>> args) { | |||
| return internal::vformat(loc, to_string_view(format_str), args); | |||
| } | |||
| template <typename S, typename... Args, typename Char = char_t<S>> | |||
| inline std::basic_string<Char> format(const std::locale& loc, | |||
| const S& format_str, Args&&... args) { | |||
| return internal::vformat( | |||
| loc, to_string_view(format_str), | |||
| {internal::make_args_checked<Args...>(format_str, args...)}); | |||
| } | |||
| template <typename S, typename OutputIt, typename... Args, | |||
| typename Char = enable_if_t< | |||
| internal::is_output_iterator<OutputIt>::value, char_t<S>>> | |||
| inline OutputIt vformat_to(OutputIt out, const std::locale& loc, | |||
| const S& format_str, | |||
| format_args_t<OutputIt, Char> args) { | |||
| using range = internal::output_range<OutputIt, Char>; | |||
| return vformat_to<arg_formatter<range>>( | |||
| range(out), to_string_view(format_str), args, internal::locale_ref(loc)); | |||
| } | |||
| template <typename OutputIt, typename S, typename... Args, | |||
| FMT_ENABLE_IF(internal::is_output_iterator<OutputIt>::value&& | |||
| internal::is_string<S>::value)> | |||
| inline OutputIt format_to(OutputIt out, const std::locale& loc, | |||
| const S& format_str, Args&&... args) { | |||
| internal::check_format_string<Args...>(format_str); | |||
| using context = format_context_t<OutputIt, char_t<S>>; | |||
| format_arg_store<context, Args...> as{args...}; | |||
| return vformat_to(out, loc, to_string_view(format_str), | |||
| basic_format_args<context>(as)); | |||
| } | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_LOCALE_H_ | |||
| @@ -0,0 +1,136 @@ | |||
| // 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_ | |||
| #include <ostream> | |||
| #include "format.h" | |||
| FMT_BEGIN_NAMESPACE | |||
| namespace internal { | |||
| template <class Char> class formatbuf : public std::basic_streambuf<Char> { | |||
| private: | |||
| using int_type = typename std::basic_streambuf<Char>::int_type; | |||
| using traits_type = typename std::basic_streambuf<Char>::traits_type; | |||
| buffer<Char>& buffer_; | |||
| public: | |||
| formatbuf(buffer<Char>& buf) : buffer_(buf) {} | |||
| protected: | |||
| // The put-area is actually 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. The obvious | |||
| // 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. | |||
| int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { | |||
| if (!traits_type::eq_int_type(ch, traits_type::eof())) | |||
| buffer_.push_back(static_cast<Char>(ch)); | |||
| return ch; | |||
| } | |||
| std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { | |||
| buffer_.append(s, s + count); | |||
| return count; | |||
| } | |||
| }; | |||
| template <typename Char> struct test_stream : std::basic_ostream<Char> { | |||
| private: | |||
| struct null; | |||
| // Hide all operator<< from std::basic_ostream<Char>. | |||
| void operator<<(null); | |||
| }; | |||
| // Checks if T has a user-defined operator<< (e.g. not a member of | |||
| // std::ostream). | |||
| template <typename T, typename Char> class is_streamable { | |||
| private: | |||
| template <typename U> | |||
| static decltype((void)(std::declval<test_stream<Char>&>() | |||
| << std::declval<U>()), | |||
| std::true_type()) | |||
| test(int); | |||
| template <typename> static std::false_type test(...); | |||
| using result = decltype(test<T>(0)); | |||
| public: | |||
| static const bool value = result::value; | |||
| }; | |||
| // Write the content of buf to os. | |||
| template <typename Char> | |||
| void write(std::basic_ostream<Char>& os, buffer<Char>& buf) { | |||
| const Char* buf_data = buf.data(); | |||
| using unsigned_streamsize = std::make_unsigned<std::streamsize>::type; | |||
| unsigned_streamsize size = buf.size(); | |||
| unsigned_streamsize max_size = | |||
| to_unsigned((std::numeric_limits<std::streamsize>::max)()); | |||
| do { | |||
| unsigned_streamsize n = size <= max_size ? size : max_size; | |||
| os.write(buf_data, static_cast<std::streamsize>(n)); | |||
| buf_data += n; | |||
| size -= n; | |||
| } while (size != 0); | |||
| } | |||
| template <typename Char, typename T> | |||
| void format_value(buffer<Char>& buf, const T& value) { | |||
| formatbuf<Char> format_buf(buf); | |||
| std::basic_ostream<Char> output(&format_buf); | |||
| output.exceptions(std::ios_base::failbit | std::ios_base::badbit); | |||
| output << value; | |||
| buf.resize(buf.size()); | |||
| } | |||
| // Formats an object of type T that has an overloaded ostream operator<<. | |||
| template <typename T, typename Char> | |||
| struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> | |||
| : formatter<basic_string_view<Char>, Char> { | |||
| template <typename Context> | |||
| auto format(const T& value, Context& ctx) -> decltype(ctx.out()) { | |||
| basic_memory_buffer<Char> buffer; | |||
| format_value(buffer, value); | |||
| basic_string_view<Char> str(buffer.data(), buffer.size()); | |||
| return formatter<basic_string_view<Char>, Char>::format(str, ctx); | |||
| } | |||
| }; | |||
| } // namespace internal | |||
| template <typename Char> | |||
| void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, | |||
| basic_format_args<buffer_context<Char>> args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| internal::vformat_to(buffer, format_str, args); | |||
| internal::write(os, buffer); | |||
| } | |||
| /** | |||
| \rst | |||
| Prints formatted data to the stream *os*. | |||
| **Example**:: | |||
| fmt::print(cerr, "Don't {}!", "panic"); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, | |||
| typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||
| void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) { | |||
| vprint(os, to_string_view(format_str), | |||
| {internal::make_args_checked<Args...>(format_str, args...)}); | |||
| } | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_OSTREAM_H_ | |||
| @@ -0,0 +1,311 @@ | |||
| // A C++ interface to POSIX functions. | |||
| // | |||
| // Copyright (c) 2012 - 2016, Victor Zverovich | |||
| // All rights reserved. | |||
| // | |||
| // For the license information refer to format.h. | |||
| #ifndef FMT_POSIX_H_ | |||
| #define FMT_POSIX_H_ | |||
| #if defined(__MINGW32__) || defined(__CYGWIN__) | |||
| // Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. | |||
| # undef __STRICT_ANSI__ | |||
| #endif | |||
| #include <errno.h> | |||
| #include <fcntl.h> // for O_RDONLY | |||
| #include <locale.h> // for locale_t | |||
| #include <stdio.h> | |||
| #include <stdlib.h> // for strtod_l | |||
| #include <cstddef> | |||
| #if defined __APPLE__ || defined(__FreeBSD__) | |||
| # include <xlocale.h> // for LC_NUMERIC_MASK on OS X | |||
| #endif | |||
| #include "format.h" | |||
| #ifndef FMT_POSIX | |||
| # if defined(_WIN32) && !defined(__MINGW32__) | |||
| // Fix warnings about deprecated symbols. | |||
| # define FMT_POSIX(call) _##call | |||
| # else | |||
| # define FMT_POSIX(call) call | |||
| # endif | |||
| #endif | |||
| // Calls to system functions are wrapped in FMT_SYSTEM for testability. | |||
| #ifdef FMT_SYSTEM | |||
| # define FMT_POSIX_CALL(call) FMT_SYSTEM(call) | |||
| #else | |||
| # define FMT_SYSTEM(call) call | |||
| # ifdef _WIN32 | |||
| // Fix warnings about deprecated symbols. | |||
| # define FMT_POSIX_CALL(call) ::_##call | |||
| # else | |||
| # define FMT_POSIX_CALL(call) ::call | |||
| # endif | |||
| #endif | |||
| // Retries the expression while it evaluates to error_result and errno | |||
| // equals to EINTR. | |||
| #ifndef _WIN32 | |||
| # define FMT_RETRY_VAL(result, expression, error_result) \ | |||
| do { \ | |||
| result = (expression); \ | |||
| } while (result == error_result && errno == EINTR) | |||
| #else | |||
| # define FMT_RETRY_VAL(result, expression, error_result) result = (expression) | |||
| #endif | |||
| #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) | |||
| FMT_BEGIN_NAMESPACE | |||
| /** | |||
| \rst | |||
| A reference to a null-terminated string. It can be constructed from a C | |||
| string or ``std::string``. | |||
| You can use one of the following type aliases for common character types: | |||
| +---------------+-----------------------------+ | |||
| | Type | Definition | | |||
| +===============+=============================+ | |||
| | cstring_view | basic_cstring_view<char> | | |||
| +---------------+-----------------------------+ | |||
| | wcstring_view | basic_cstring_view<wchar_t> | | |||
| +---------------+-----------------------------+ | |||
| This class is most useful as a parameter type to allow passing | |||
| different types of strings to a function, for example:: | |||
| template <typename... Args> | |||
| std::string format(cstring_view format_str, const Args & ... args); | |||
| format("{}", 42); | |||
| format(std::string("{}"), 42); | |||
| \endrst | |||
| */ | |||
| template <typename Char> class basic_cstring_view { | |||
| private: | |||
| const Char* data_; | |||
| public: | |||
| /** Constructs a string reference object from a C string. */ | |||
| basic_cstring_view(const Char* s) : data_(s) {} | |||
| /** | |||
| \rst | |||
| Constructs a string reference from an ``std::string`` object. | |||
| \endrst | |||
| */ | |||
| basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {} | |||
| /** Returns the pointer to a C string. */ | |||
| const Char* c_str() const { return data_; } | |||
| }; | |||
| using cstring_view = basic_cstring_view<char>; | |||
| using wcstring_view = basic_cstring_view<wchar_t>; | |||
| // An error code. | |||
| class error_code { | |||
| private: | |||
| int value_; | |||
| public: | |||
| explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} | |||
| int get() const FMT_NOEXCEPT { return value_; } | |||
| }; | |||
| // A buffered file. | |||
| class buffered_file { | |||
| private: | |||
| FILE* file_; | |||
| friend class file; | |||
| explicit buffered_file(FILE* f) : file_(f) {} | |||
| public: | |||
| // Constructs a buffered_file object which doesn't represent any file. | |||
| buffered_file() FMT_NOEXCEPT : file_(nullptr) {} | |||
| // Destroys the object closing the file it represents if any. | |||
| FMT_API ~buffered_file() FMT_NOEXCEPT; | |||
| private: | |||
| buffered_file(const buffered_file&) = delete; | |||
| void operator=(const buffered_file&) = delete; | |||
| public: | |||
| buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) { | |||
| other.file_ = nullptr; | |||
| } | |||
| buffered_file& operator=(buffered_file&& other) { | |||
| close(); | |||
| file_ = other.file_; | |||
| other.file_ = nullptr; | |||
| return *this; | |||
| } | |||
| // Opens a file. | |||
| FMT_API buffered_file(cstring_view filename, cstring_view mode); | |||
| // Closes the file. | |||
| FMT_API void close(); | |||
| // Returns the pointer to a FILE object representing this file. | |||
| FILE* get() const FMT_NOEXCEPT { return file_; } | |||
| // We place parentheses around fileno to workaround a bug in some versions | |||
| // of MinGW that define fileno as a macro. | |||
| FMT_API int(fileno)() const; | |||
| void vprint(string_view format_str, format_args args) { | |||
| fmt::vprint(file_, format_str, args); | |||
| } | |||
| template <typename... Args> | |||
| inline void print(string_view format_str, const Args&... args) { | |||
| vprint(format_str, make_format_args(args...)); | |||
| } | |||
| }; | |||
| // A file. Closed file is represented by a file object with descriptor -1. | |||
| // Methods that are not declared with FMT_NOEXCEPT may throw | |||
| // fmt::system_error in case of failure. Note that some errors such as | |||
| // closing the file multiple times will cause a crash on Windows rather | |||
| // than an exception. You can get standard behavior by overriding the | |||
| // invalid parameter handler with _set_invalid_parameter_handler. | |||
| class file { | |||
| private: | |||
| int fd_; // File descriptor. | |||
| // Constructs a file object with a given descriptor. | |||
| explicit file(int fd) : fd_(fd) {} | |||
| public: | |||
| // Possible values for the oflag argument to the constructor. | |||
| enum { | |||
| RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. | |||
| WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. | |||
| RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. | |||
| }; | |||
| // Constructs a file object which doesn't represent any file. | |||
| file() FMT_NOEXCEPT : fd_(-1) {} | |||
| // Opens a file and constructs a file object representing this file. | |||
| FMT_API file(cstring_view path, int oflag); | |||
| private: | |||
| file(const file&) = delete; | |||
| void operator=(const file&) = delete; | |||
| public: | |||
| file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; } | |||
| file& operator=(file&& other) { | |||
| close(); | |||
| fd_ = other.fd_; | |||
| other.fd_ = -1; | |||
| return *this; | |||
| } | |||
| // Destroys the object closing the file it represents if any. | |||
| FMT_API ~file() FMT_NOEXCEPT; | |||
| // Returns the file descriptor. | |||
| int descriptor() const FMT_NOEXCEPT { return fd_; } | |||
| // Closes the file. | |||
| FMT_API void close(); | |||
| // Returns the file size. The size has signed type for consistency with | |||
| // stat::st_size. | |||
| FMT_API long long size() const; | |||
| // Attempts to read count bytes from the file into the specified buffer. | |||
| FMT_API std::size_t read(void* buffer, std::size_t count); | |||
| // Attempts to write count bytes from the specified buffer to the file. | |||
| FMT_API std::size_t write(const void* buffer, std::size_t count); | |||
| // Duplicates a file descriptor with the dup function and returns | |||
| // the duplicate as a file object. | |||
| FMT_API static file dup(int fd); | |||
| // Makes fd be the copy of this file descriptor, closing fd first if | |||
| // necessary. | |||
| FMT_API void dup2(int fd); | |||
| // Makes fd be the copy of this file descriptor, closing fd first if | |||
| // necessary. | |||
| FMT_API void dup2(int fd, error_code& ec) FMT_NOEXCEPT; | |||
| // Creates a pipe setting up read_end and write_end file objects for reading | |||
| // and writing respectively. | |||
| FMT_API static void pipe(file& read_end, file& write_end); | |||
| // Creates a buffered_file object associated with this file and detaches | |||
| // this file object from the file. | |||
| FMT_API buffered_file fdopen(const char* mode); | |||
| }; | |||
| // Returns the memory page size. | |||
| long getpagesize(); | |||
| #ifdef FMT_LOCALE | |||
| // A "C" numeric locale. | |||
| class Locale { | |||
| private: | |||
| # ifdef _WIN32 | |||
| using locale_t = _locale_t; | |||
| enum { LC_NUMERIC_MASK = LC_NUMERIC }; | |||
| static locale_t newlocale(int category_mask, const char* locale, locale_t) { | |||
| return _create_locale(category_mask, locale); | |||
| } | |||
| static void freelocale(locale_t locale) { _free_locale(locale); } | |||
| static double strtod_l(const char* nptr, char** endptr, _locale_t locale) { | |||
| return _strtod_l(nptr, endptr, locale); | |||
| } | |||
| # endif | |||
| locale_t locale_; | |||
| Locale(const Locale&) = delete; | |||
| void operator=(const Locale&) = delete; | |||
| public: | |||
| using type = locale_t; | |||
| Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) { | |||
| if (!locale_) FMT_THROW(system_error(errno, "cannot create locale")); | |||
| } | |||
| ~Locale() { freelocale(locale_); } | |||
| type get() const { return locale_; } | |||
| // Converts string to floating-point number and advances str past the end | |||
| // of the parsed input. | |||
| double strtod(const char*& str) const { | |||
| char* end = nullptr; | |||
| double result = strtod_l(str, &end, locale_); | |||
| str = end; | |||
| return result; | |||
| } | |||
| }; | |||
| #endif // FMT_LOCALE | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_POSIX_H_ | |||
| @@ -0,0 +1,715 @@ | |||
| // Formatting library for C++ | |||
| // | |||
| // 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_ | |||
| #include <algorithm> // std::fill_n | |||
| #include <limits> // std::numeric_limits | |||
| #include "ostream.h" | |||
| FMT_BEGIN_NAMESPACE | |||
| namespace internal { | |||
| // A helper function to suppress bogus "conditional expression is constant" | |||
| // warnings. | |||
| template <typename T> inline T const_check(T value) { return value; } | |||
| // Checks if a value fits in int - used to avoid warnings about comparing | |||
| // signed and unsigned integers. | |||
| template <bool IsSigned> struct int_checker { | |||
| template <typename T> static bool fits_in_int(T value) { | |||
| unsigned max = std::numeric_limits<int>::max(); | |||
| return value <= max; | |||
| } | |||
| static bool fits_in_int(bool) { return true; } | |||
| }; | |||
| template <> struct int_checker<true> { | |||
| template <typename T> static bool fits_in_int(T value) { | |||
| return value >= std::numeric_limits<int>::min() && | |||
| value <= std::numeric_limits<int>::max(); | |||
| } | |||
| static bool fits_in_int(int) { return true; } | |||
| }; | |||
| class printf_precision_handler { | |||
| public: | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| int operator()(T value) { | |||
| if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) | |||
| FMT_THROW(format_error("number is too big")); | |||
| return (std::max)(static_cast<int>(value), 0); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||
| int operator()(T) { | |||
| FMT_THROW(format_error("precision is not integer")); | |||
| return 0; | |||
| } | |||
| }; | |||
| // An argument visitor that returns true iff arg is a zero integer. | |||
| class is_zero_int { | |||
| public: | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| bool operator()(T value) { | |||
| return value == 0; | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||
| bool operator()(T) { | |||
| return false; | |||
| } | |||
| }; | |||
| template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {}; | |||
| template <> struct make_unsigned_or_bool<bool> { using type = bool; }; | |||
| template <typename T, typename Context> class arg_converter { | |||
| private: | |||
| using char_type = typename Context::char_type; | |||
| basic_format_arg<Context>& arg_; | |||
| char_type type_; | |||
| public: | |||
| arg_converter(basic_format_arg<Context>& arg, char_type type) | |||
| : arg_(arg), type_(type) {} | |||
| void operator()(bool value) { | |||
| if (type_ != 's') operator()<bool>(value); | |||
| } | |||
| template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)> | |||
| void operator()(U value) { | |||
| bool is_signed = type_ == 'd' || type_ == 'i'; | |||
| using target_type = conditional_t<std::is_same<T, void>::value, U, T>; | |||
| if (const_check(sizeof(target_type) <= sizeof(int))) { | |||
| // Extra casts are used to silence warnings. | |||
| if (is_signed) { | |||
| arg_ = internal::make_arg<Context>( | |||
| static_cast<int>(static_cast<target_type>(value))); | |||
| } else { | |||
| using unsigned_type = typename make_unsigned_or_bool<target_type>::type; | |||
| arg_ = internal::make_arg<Context>( | |||
| static_cast<unsigned>(static_cast<unsigned_type>(value))); | |||
| } | |||
| } else { | |||
| if (is_signed) { | |||
| // 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. | |||
| arg_ = internal::make_arg<Context>(static_cast<long long>(value)); | |||
| } else { | |||
| arg_ = internal::make_arg<Context>( | |||
| static_cast<typename make_unsigned_or_bool<U>::type>(value)); | |||
| } | |||
| } | |||
| } | |||
| template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::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 <typename T, typename Context, typename Char> | |||
| void convert_arg(basic_format_arg<Context>& arg, Char type) { | |||
| visit_format_arg(arg_converter<T, Context>(arg, type), arg); | |||
| } | |||
| // Converts an integer argument to char for printf. | |||
| template <typename Context> class char_converter { | |||
| private: | |||
| basic_format_arg<Context>& arg_; | |||
| public: | |||
| explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {} | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| void operator()(T value) { | |||
| arg_ = internal::make_arg<Context>( | |||
| static_cast<typename Context::char_type>(value)); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||
| void operator()(T) {} // No conversion needed for non-integral types. | |||
| }; | |||
| // Checks if an argument is a valid printf width specifier and sets | |||
| // left alignment if it is negative. | |||
| template <typename Char> class printf_width_handler { | |||
| private: | |||
| using format_specs = basic_format_specs<Char>; | |||
| format_specs& specs_; | |||
| public: | |||
| explicit printf_width_handler(format_specs& specs) : specs_(specs) {} | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| unsigned operator()(T value) { | |||
| auto width = static_cast<uint32_or_64_t<T>>(value); | |||
| if (internal::is_negative(value)) { | |||
| specs_.align = align::left; | |||
| width = 0 - width; | |||
| } | |||
| unsigned int_max = std::numeric_limits<int>::max(); | |||
| if (width > int_max) FMT_THROW(format_error("number is too big")); | |||
| return static_cast<unsigned>(width); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)> | |||
| unsigned operator()(T) { | |||
| FMT_THROW(format_error("width is not integer")); | |||
| return 0; | |||
| } | |||
| }; | |||
| template <typename Char, typename Context> | |||
| void printf(buffer<Char>& buf, basic_string_view<Char> format, | |||
| basic_format_args<Context> args) { | |||
| Context(std::back_inserter(buf), format, args).format(); | |||
| } | |||
| template <typename OutputIt, typename Char, typename Context> | |||
| internal::truncating_iterator<OutputIt> printf( | |||
| internal::truncating_iterator<OutputIt> it, basic_string_view<Char> format, | |||
| basic_format_args<Context> args) { | |||
| return Context(it, format, args).format(); | |||
| } | |||
| } // namespace internal | |||
| using internal::printf; // For printing into memory_buffer. | |||
| template <typename Range> class printf_arg_formatter; | |||
| template <typename OutputIt, typename Char> class basic_printf_context; | |||
| /** | |||
| \rst | |||
| The ``printf`` argument formatter. | |||
| \endrst | |||
| */ | |||
| template <typename Range> | |||
| class printf_arg_formatter : public internal::arg_formatter_base<Range> { | |||
| public: | |||
| using iterator = typename Range::iterator; | |||
| private: | |||
| using char_type = typename Range::value_type; | |||
| using base = internal::arg_formatter_base<Range>; | |||
| using context_type = basic_printf_context<iterator, char_type>; | |||
| context_type& context_; | |||
| void write_null_pointer(char) { | |||
| this->specs()->type = 0; | |||
| this->write("(nil)"); | |||
| } | |||
| void write_null_pointer(wchar_t) { | |||
| this->specs()->type = 0; | |||
| this->write(L"(nil)"); | |||
| } | |||
| public: | |||
| using format_specs = typename base::format_specs; | |||
| /** | |||
| \rst | |||
| Constructs an argument formatter object. | |||
| *buffer* is a reference to the output buffer and *specs* contains format | |||
| specifier information for standard argument types. | |||
| \endrst | |||
| */ | |||
| printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx) | |||
| : base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {} | |||
| template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> | |||
| iterator operator()(T value) { | |||
| // MSVC2013 fails to compile separate overloads for bool and char_type so | |||
| // use std::is_same instead. | |||
| if (std::is_same<T, bool>::value) { | |||
| format_specs& fmt_specs = *this->specs(); | |||
| if (fmt_specs.type != 's') return base::operator()(value ? 1 : 0); | |||
| fmt_specs.type = 0; | |||
| this->write(value != 0); | |||
| } else if (std::is_same<T, char_type>::value) { | |||
| format_specs& fmt_specs = *this->specs(); | |||
| if (fmt_specs.type && fmt_specs.type != 'c') | |||
| return (*this)(static_cast<int>(value)); | |||
| fmt_specs.sign = sign::none; | |||
| fmt_specs.alt = false; | |||
| fmt_specs.align = align::right; | |||
| return base::operator()(value); | |||
| } else { | |||
| return base::operator()(value); | |||
| } | |||
| return this->out(); | |||
| } | |||
| template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)> | |||
| iterator operator()(T value) { | |||
| return base::operator()(value); | |||
| } | |||
| /** Formats a null-terminated C string. */ | |||
| iterator operator()(const char* value) { | |||
| if (value) | |||
| base::operator()(value); | |||
| else if (this->specs()->type == 'p') | |||
| write_null_pointer(char_type()); | |||
| else | |||
| this->write("(null)"); | |||
| return this->out(); | |||
| } | |||
| /** Formats a null-terminated wide C string. */ | |||
| iterator operator()(const wchar_t* value) { | |||
| if (value) | |||
| base::operator()(value); | |||
| else if (this->specs()->type == 'p') | |||
| write_null_pointer(char_type()); | |||
| else | |||
| this->write(L"(null)"); | |||
| return this->out(); | |||
| } | |||
| iterator operator()(basic_string_view<char_type> value) { | |||
| return base::operator()(value); | |||
| } | |||
| iterator operator()(monostate value) { return base::operator()(value); } | |||
| /** Formats a pointer. */ | |||
| iterator operator()(const void* value) { | |||
| if (value) return base::operator()(value); | |||
| this->specs()->type = 0; | |||
| write_null_pointer(char_type()); | |||
| return this->out(); | |||
| } | |||
| /** Formats an argument of a custom (user-defined) type. */ | |||
| iterator operator()(typename basic_format_arg<context_type>::handle handle) { | |||
| handle.format(context_.parse_context(), context_); | |||
| return this->out(); | |||
| } | |||
| }; | |||
| template <typename T> struct printf_formatter { | |||
| template <typename ParseContext> | |||
| auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||
| return ctx.begin(); | |||
| } | |||
| template <typename FormatContext> | |||
| auto format(const T& value, FormatContext& ctx) -> decltype(ctx.out()) { | |||
| internal::format_value(internal::get_container(ctx.out()), value); | |||
| return ctx.out(); | |||
| } | |||
| }; | |||
| /** This template formats data and writes the output to a writer. */ | |||
| template <typename OutputIt, typename Char> class basic_printf_context { | |||
| public: | |||
| /** The character type for the output. */ | |||
| using char_type = Char; | |||
| using format_arg = basic_format_arg<basic_printf_context>; | |||
| template <typename T> using formatter_type = printf_formatter<T>; | |||
| private: | |||
| using format_specs = basic_format_specs<char_type>; | |||
| OutputIt out_; | |||
| basic_format_args<basic_printf_context> args_; | |||
| basic_parse_context<Char> parse_ctx_; | |||
| static void parse_flags(format_specs& specs, const Char*& it, | |||
| const Char* end); | |||
| // Returns the argument with specified index or, if arg_index is equal | |||
| // to the maximum unsigned value, the next argument. | |||
| format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max()); | |||
| // Parses argument index, flags and width and returns the argument index. | |||
| unsigned parse_header(const Char*& it, const Char* end, format_specs& specs); | |||
| public: | |||
| /** | |||
| \rst | |||
| Constructs a ``printf_context`` object. References to the arguments and | |||
| the writer are stored in the context object so make sure they have | |||
| appropriate lifetimes. | |||
| \endrst | |||
| */ | |||
| basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, | |||
| basic_format_args<basic_printf_context> args) | |||
| : out_(out), args_(args), parse_ctx_(format_str) {} | |||
| OutputIt out() { return out_; } | |||
| void advance_to(OutputIt it) { out_ = it; } | |||
| format_arg arg(unsigned id) const { return args_.get(id); } | |||
| basic_parse_context<Char>& parse_context() { return parse_ctx_; } | |||
| FMT_CONSTEXPR void on_error(const char* message) { | |||
| parse_ctx_.on_error(message); | |||
| } | |||
| /** Formats stored arguments and writes the output to the range. */ | |||
| template <typename ArgFormatter = | |||
| printf_arg_formatter<internal::buffer_range<Char>>> | |||
| OutputIt format(); | |||
| }; | |||
| template <typename OutputIt, typename Char> | |||
| void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs, | |||
| const Char*& it, | |||
| const Char* end) { | |||
| for (; it != end; ++it) { | |||
| switch (*it) { | |||
| case '-': | |||
| specs.align = align::left; | |||
| break; | |||
| case '+': | |||
| specs.sign = sign::plus; | |||
| break; | |||
| case '0': | |||
| specs.fill[0] = '0'; | |||
| break; | |||
| case ' ': | |||
| specs.sign = sign::space; | |||
| break; | |||
| case '#': | |||
| specs.alt = true; | |||
| break; | |||
| default: | |||
| return; | |||
| } | |||
| } | |||
| } | |||
| template <typename OutputIt, typename Char> | |||
| typename basic_printf_context<OutputIt, Char>::format_arg | |||
| basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) { | |||
| if (arg_index == std::numeric_limits<unsigned>::max()) | |||
| arg_index = parse_ctx_.next_arg_id(); | |||
| else | |||
| parse_ctx_.check_arg_id(--arg_index); | |||
| return internal::get_arg(*this, arg_index); | |||
| } | |||
| template <typename OutputIt, typename Char> | |||
| unsigned basic_printf_context<OutputIt, Char>::parse_header( | |||
| const Char*& it, const Char* end, format_specs& specs) { | |||
| unsigned arg_index = std::numeric_limits<unsigned>::max(); | |||
| char_type c = *it; | |||
| if (c >= '0' && c <= '9') { | |||
| // Parse an argument index (if followed by '$') or a width possibly | |||
| // preceded with '0' flag(s). | |||
| internal::error_handler eh; | |||
| unsigned value = parse_nonnegative_int(it, end, eh); | |||
| if (it != end && *it == '$') { // value is an argument index | |||
| ++it; | |||
| arg_index = value; | |||
| } else { | |||
| if (c == '0') specs.fill[0] = '0'; | |||
| if (value != 0) { | |||
| // Nonzero value means that we parsed width and don't need to | |||
| // parse it or flags again, so return now. | |||
| specs.width = value; | |||
| return arg_index; | |||
| } | |||
| } | |||
| } | |||
| parse_flags(specs, it, end); | |||
| // Parse width. | |||
| if (it != end) { | |||
| if (*it >= '0' && *it <= '9') { | |||
| internal::error_handler eh; | |||
| specs.width = parse_nonnegative_int(it, end, eh); | |||
| } else if (*it == '*') { | |||
| ++it; | |||
| specs.width = visit_format_arg( | |||
| internal::printf_width_handler<char_type>(specs), get_arg()); | |||
| } | |||
| } | |||
| return arg_index; | |||
| } | |||
| template <typename OutputIt, typename Char> | |||
| template <typename ArgFormatter> | |||
| OutputIt basic_printf_context<OutputIt, Char>::format() { | |||
| auto out = this->out(); | |||
| const Char* start = parse_ctx_.begin(); | |||
| const Char* end = parse_ctx_.end(); | |||
| auto it = start; | |||
| while (it != end) { | |||
| char_type c = *it++; | |||
| if (c != '%') continue; | |||
| if (it != end && *it == c) { | |||
| out = std::copy(start, it, out); | |||
| start = ++it; | |||
| continue; | |||
| } | |||
| out = std::copy(start, it - 1, out); | |||
| format_specs specs; | |||
| specs.align = align::right; | |||
| // Parse argument index, flags and width. | |||
| unsigned arg_index = parse_header(it, end, specs); | |||
| // Parse precision. | |||
| if (it != end && *it == '.') { | |||
| ++it; | |||
| c = it != end ? *it : 0; | |||
| if ('0' <= c && c <= '9') { | |||
| internal::error_handler eh; | |||
| specs.precision = static_cast<int>(parse_nonnegative_int(it, end, eh)); | |||
| } else if (c == '*') { | |||
| ++it; | |||
| specs.precision = | |||
| visit_format_arg(internal::printf_precision_handler(), get_arg()); | |||
| } else { | |||
| specs.precision = 0; | |||
| } | |||
| } | |||
| format_arg arg = get_arg(arg_index); | |||
| if (specs.alt && visit_format_arg(internal::is_zero_int(), arg)) | |||
| specs.alt = false; | |||
| if (specs.fill[0] == '0') { | |||
| if (arg.is_arithmetic()) | |||
| specs.align = align::numeric; | |||
| else | |||
| specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types. | |||
| } | |||
| // Parse length and convert the argument to the required type. | |||
| c = it != end ? *it++ : 0; | |||
| char_type t = it != end ? *it : 0; | |||
| using internal::convert_arg; | |||
| switch (c) { | |||
| case 'h': | |||
| if (t == 'h') { | |||
| ++it; | |||
| t = it != end ? *it : 0; | |||
| convert_arg<signed char>(arg, t); | |||
| } else { | |||
| convert_arg<short>(arg, t); | |||
| } | |||
| break; | |||
| case 'l': | |||
| if (t == 'l') { | |||
| ++it; | |||
| t = it != end ? *it : 0; | |||
| convert_arg<long long>(arg, t); | |||
| } else { | |||
| convert_arg<long>(arg, t); | |||
| } | |||
| break; | |||
| case 'j': | |||
| convert_arg<intmax_t>(arg, t); | |||
| break; | |||
| case 'z': | |||
| convert_arg<std::size_t>(arg, t); | |||
| break; | |||
| case 't': | |||
| convert_arg<std::ptrdiff_t>(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<void>(arg, c); | |||
| } | |||
| // Parse type. | |||
| if (it == end) FMT_THROW(format_error("invalid format string")); | |||
| specs.type = static_cast<char>(*it++); | |||
| if (arg.is_integral()) { | |||
| // Normalize type. | |||
| switch (specs.type) { | |||
| case 'i': | |||
| case 'u': | |||
| specs.type = 'd'; | |||
| break; | |||
| case 'c': | |||
| visit_format_arg(internal::char_converter<basic_printf_context>(arg), | |||
| arg); | |||
| break; | |||
| } | |||
| } | |||
| start = it; | |||
| // Format argument. | |||
| visit_format_arg(ArgFormatter(out, specs, *this), arg); | |||
| } | |||
| return std::copy(start, it, out); | |||
| } | |||
| template <typename Char> | |||
| using basic_printf_context_t = | |||
| basic_printf_context<std::back_insert_iterator<internal::buffer<Char>>, | |||
| Char>; | |||
| using printf_context = basic_printf_context_t<char>; | |||
| using wprintf_context = basic_printf_context_t<wchar_t>; | |||
| using printf_args = basic_format_args<printf_context>; | |||
| using wprintf_args = basic_format_args<wprintf_context>; | |||
| /** | |||
| \rst | |||
| Constructs an `~fmt::format_arg_store` object that contains references to | |||
| arguments and can be implicitly converted to `~fmt::printf_args`. | |||
| \endrst | |||
| */ | |||
| template <typename... Args> | |||
| inline format_arg_store<printf_context, Args...> make_printf_args( | |||
| const Args&... args) { | |||
| return {args...}; | |||
| } | |||
| /** | |||
| \rst | |||
| Constructs an `~fmt::format_arg_store` object that contains references to | |||
| arguments and can be implicitly converted to `~fmt::wprintf_args`. | |||
| \endrst | |||
| */ | |||
| template <typename... Args> | |||
| inline format_arg_store<wprintf_context, Args...> make_wprintf_args( | |||
| const Args&... args) { | |||
| return {args...}; | |||
| } | |||
| template <typename S, typename Char = char_t<S>> | |||
| inline std::basic_string<Char> vsprintf( | |||
| const S& format, basic_format_args<basic_printf_context_t<Char>> args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| printf(buffer, to_string_view(format), args); | |||
| return to_string(buffer); | |||
| } | |||
| /** | |||
| \rst | |||
| Formats arguments and returns the result as a string. | |||
| **Example**:: | |||
| std::string message = fmt::sprintf("The answer is %d", 42); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, | |||
| typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||
| inline std::basic_string<Char> sprintf(const S& format, const Args&... args) { | |||
| using context = basic_printf_context_t<Char>; | |||
| return vsprintf(to_string_view(format), {make_format_args<context>(args...)}); | |||
| } | |||
| template <typename S, typename Char = char_t<S>> | |||
| inline int vfprintf(std::FILE* f, const S& format, | |||
| basic_format_args<basic_printf_context_t<Char>> args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| printf(buffer, to_string_view(format), args); | |||
| std::size_t size = buffer.size(); | |||
| return std::fwrite(buffer.data(), sizeof(Char), size, f) < size | |||
| ? -1 | |||
| : static_cast<int>(size); | |||
| } | |||
| /** | |||
| \rst | |||
| Prints formatted data to the file *f*. | |||
| **Example**:: | |||
| fmt::fprintf(stderr, "Don't %s!", "panic"); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, | |||
| typename Char = enable_if_t<internal::is_string<S>::value, char_t<S>>> | |||
| inline int fprintf(std::FILE* f, const S& format, const Args&... args) { | |||
| using context = basic_printf_context_t<Char>; | |||
| return vfprintf(f, to_string_view(format), | |||
| {make_format_args<context>(args...)}); | |||
| } | |||
| template <typename S, typename Char = char_t<S>> | |||
| inline int vprintf(const S& format, | |||
| basic_format_args<basic_printf_context_t<Char>> args) { | |||
| return vfprintf(stdout, to_string_view(format), args); | |||
| } | |||
| /** | |||
| \rst | |||
| Prints formatted data to ``stdout``. | |||
| **Example**:: | |||
| fmt::printf("Elapsed time: %.2f seconds", 1.23); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, | |||
| FMT_ENABLE_IF(internal::is_string<S>::value)> | |||
| inline int printf(const S& format_str, const Args&... args) { | |||
| using context = basic_printf_context_t<char_t<S>>; | |||
| return vprintf(to_string_view(format_str), | |||
| {make_format_args<context>(args...)}); | |||
| } | |||
| template <typename S, typename Char = char_t<S>> | |||
| inline int vfprintf(std::basic_ostream<Char>& os, const S& format, | |||
| basic_format_args<basic_printf_context_t<Char>> args) { | |||
| basic_memory_buffer<Char> buffer; | |||
| printf(buffer, to_string_view(format), args); | |||
| internal::write(os, buffer); | |||
| return static_cast<int>(buffer.size()); | |||
| } | |||
| /** Formats arguments and writes the output to the range. */ | |||
| template <typename ArgFormatter, typename Char, | |||
| typename Context = | |||
| basic_printf_context<typename ArgFormatter::iterator, Char>> | |||
| typename ArgFormatter::iterator vprintf(internal::buffer<Char>& out, | |||
| basic_string_view<Char> format_str, | |||
| basic_format_args<Context> args) { | |||
| typename ArgFormatter::iterator iter(out); | |||
| Context(iter, format_str, args).template format<ArgFormatter>(); | |||
| return iter; | |||
| } | |||
| /** | |||
| \rst | |||
| Prints formatted data to the stream *os*. | |||
| **Example**:: | |||
| fmt::fprintf(cerr, "Don't %s!", "panic"); | |||
| \endrst | |||
| */ | |||
| template <typename S, typename... Args, typename Char = char_t<S>> | |||
| inline int fprintf(std::basic_ostream<Char>& os, const S& format_str, | |||
| const Args&... args) { | |||
| using context = basic_printf_context_t<Char>; | |||
| return vfprintf(os, to_string_view(format_str), | |||
| {make_format_args<context>(args...)}); | |||
| } | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_PRINTF_H_ | |||
| @@ -0,0 +1,288 @@ | |||
| // Formatting library for C++ - experimental range support | |||
| // | |||
| // Copyright (c) 2012 - present, Victor Zverovich | |||
| // All rights reserved. | |||
| // | |||
| // For the license information refer to format.h. | |||
| // | |||
| // Copyright (c) 2018 - present, Remotion (Igor Schulz) | |||
| // All Rights Reserved | |||
| // {fmt} support for ranges, containers and types tuple interface. | |||
| #ifndef FMT_RANGES_H_ | |||
| #define FMT_RANGES_H_ | |||
| #include <type_traits> | |||
| #include "format.h" | |||
| // output only up to N items from the range. | |||
| #ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT | |||
| # define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 | |||
| #endif | |||
| FMT_BEGIN_NAMESPACE | |||
| template <typename Char> struct formatting_base { | |||
| template <typename ParseContext> | |||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||
| return ctx.begin(); | |||
| } | |||
| }; | |||
| template <typename Char, typename Enable = void> | |||
| struct formatting_range : formatting_base<Char> { | |||
| static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = | |||
| FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the | |||
| // range. | |||
| Char prefix; | |||
| Char delimiter; | |||
| Char postfix; | |||
| formatting_range() : prefix('{'), delimiter(','), postfix('}') {} | |||
| static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||
| static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||
| }; | |||
| template <typename Char, typename Enable = void> | |||
| struct formatting_tuple : formatting_base<Char> { | |||
| Char prefix; | |||
| Char delimiter; | |||
| Char postfix; | |||
| formatting_tuple() : prefix('('), delimiter(','), postfix(')') {} | |||
| static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; | |||
| static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; | |||
| }; | |||
| namespace internal { | |||
| template <typename RangeT, typename OutputIterator> | |||
| OutputIterator copy(const RangeT& range, OutputIterator out) { | |||
| for (auto it = range.begin(), end = range.end(); it != end; ++it) | |||
| *out++ = *it; | |||
| return out; | |||
| } | |||
| template <typename OutputIterator> | |||
| OutputIterator copy(const char* str, OutputIterator out) { | |||
| while (*str) *out++ = *str++; | |||
| return out; | |||
| } | |||
| template <typename OutputIterator> | |||
| OutputIterator copy(char ch, OutputIterator out) { | |||
| *out++ = ch; | |||
| return out; | |||
| } | |||
| /// Return true value if T has std::string interface, like std::string_view. | |||
| template <typename T> class is_like_std_string { | |||
| template <typename U> | |||
| static auto check(U* p) | |||
| -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); | |||
| template <typename> static void check(...); | |||
| public: | |||
| static FMT_CONSTEXPR_DECL const bool value = | |||
| is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; | |||
| }; | |||
| template <typename Char> | |||
| struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; | |||
| template <typename... Ts> struct conditional_helper {}; | |||
| template <typename T, typename _ = void> struct is_range_ : std::false_type {}; | |||
| #if !FMT_MSC_VER || FMT_MSC_VER > 1800 | |||
| template <typename T> | |||
| struct is_range_< | |||
| T, conditional_t<false, | |||
| conditional_helper<decltype(std::declval<T>().begin()), | |||
| decltype(std::declval<T>().end())>, | |||
| void>> : std::true_type {}; | |||
| #endif | |||
| /// tuple_size and tuple_element check. | |||
| template <typename T> class is_tuple_like_ { | |||
| template <typename U> | |||
| static auto check(U* p) | |||
| -> decltype(std::tuple_size<U>::value, | |||
| (void)std::declval<typename std::tuple_element<0, U>::type>(), | |||
| int()); | |||
| template <typename> static void check(...); | |||
| public: | |||
| static FMT_CONSTEXPR_DECL const bool value = | |||
| !std::is_void<decltype(check<T>(nullptr))>::value; | |||
| }; | |||
| // Check for integer_sequence | |||
| #if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 | |||
| template <typename T, T... N> | |||
| using integer_sequence = std::integer_sequence<T, N...>; | |||
| template <std::size_t... N> using index_sequence = std::index_sequence<N...>; | |||
| template <std::size_t N> | |||
| using make_index_sequence = std::make_index_sequence<N>; | |||
| #else | |||
| template <typename T, T... N> struct integer_sequence { | |||
| using value_type = T; | |||
| static FMT_CONSTEXPR std::size_t size() { return sizeof...(N); } | |||
| }; | |||
| template <std::size_t... N> | |||
| using index_sequence = integer_sequence<std::size_t, N...>; | |||
| template <typename T, std::size_t N, T... Ns> | |||
| struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; | |||
| template <typename T, T... Ns> | |||
| struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; | |||
| template <std::size_t N> | |||
| using make_index_sequence = make_integer_sequence<std::size_t, N>; | |||
| #endif | |||
| template <class Tuple, class F, size_t... Is> | |||
| void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT { | |||
| using std::get; | |||
| // using free function get<I>(T) now. | |||
| const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; | |||
| (void)_; // blocks warnings | |||
| } | |||
| template <class T> | |||
| FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes( | |||
| T const&) { | |||
| return {}; | |||
| } | |||
| template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) { | |||
| const auto indexes = get_indexes(tup); | |||
| for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); | |||
| } | |||
| template <typename Arg, FMT_ENABLE_IF(!is_like_std_string< | |||
| typename std::decay<Arg>::type>::value)> | |||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||
| return add_space ? " {}" : "{}"; | |||
| } | |||
| template <typename Arg, FMT_ENABLE_IF(is_like_std_string< | |||
| typename std::decay<Arg>::type>::value)> | |||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&) { | |||
| return add_space ? " \"{}\"" : "\"{}\""; | |||
| } | |||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { | |||
| return add_space ? " \"{}\"" : "\"{}\""; | |||
| } | |||
| FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { | |||
| return add_space ? L" \"{}\"" : L"\"{}\""; | |||
| } | |||
| FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { | |||
| return add_space ? " '{}'" : "'{}'"; | |||
| } | |||
| FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { | |||
| return add_space ? L" '{}'" : L"'{}'"; | |||
| } | |||
| } // namespace internal | |||
| template <typename T> struct is_tuple_like { | |||
| static FMT_CONSTEXPR_DECL const bool value = | |||
| internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; | |||
| }; | |||
| template <typename TupleT, typename Char> | |||
| struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { | |||
| private: | |||
| // C++11 generic lambda for format() | |||
| template <typename FormatContext> struct format_each { | |||
| template <typename T> void operator()(const T& v) { | |||
| if (i > 0) { | |||
| if (formatting.add_prepostfix_space) { | |||
| *out++ = ' '; | |||
| } | |||
| out = internal::copy(formatting.delimiter, out); | |||
| } | |||
| out = format_to(out, | |||
| internal::format_str_quoted( | |||
| (formatting.add_delimiter_spaces && i > 0), v), | |||
| v); | |||
| ++i; | |||
| } | |||
| formatting_tuple<Char>& formatting; | |||
| std::size_t& i; | |||
| typename std::add_lvalue_reference<decltype( | |||
| std::declval<FormatContext>().out())>::type out; | |||
| }; | |||
| public: | |||
| formatting_tuple<Char> formatting; | |||
| template <typename ParseContext> | |||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||
| return formatting.parse(ctx); | |||
| } | |||
| template <typename FormatContext = format_context> | |||
| auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { | |||
| auto out = ctx.out(); | |||
| std::size_t i = 0; | |||
| internal::copy(formatting.prefix, out); | |||
| internal::for_each(values, format_each<FormatContext>{formatting, i, out}); | |||
| if (formatting.add_prepostfix_space) { | |||
| *out++ = ' '; | |||
| } | |||
| internal::copy(formatting.postfix, out); | |||
| return ctx.out(); | |||
| } | |||
| }; | |||
| template <typename T, typename Char> struct is_range { | |||
| static FMT_CONSTEXPR_DECL const bool value = | |||
| internal::is_range_<T>::value && | |||
| !internal::is_like_std_string<T>::value && | |||
| !std::is_convertible<T, std::basic_string<Char>>::value; | |||
| }; | |||
| template <typename RangeT, typename Char> | |||
| struct formatter<RangeT, Char, | |||
| enable_if_t<fmt::is_range<RangeT, Char>::value>> { | |||
| formatting_range<Char> formatting; | |||
| template <typename ParseContext> | |||
| FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { | |||
| return formatting.parse(ctx); | |||
| } | |||
| template <typename FormatContext> | |||
| typename FormatContext::iterator format(const RangeT& values, | |||
| FormatContext& ctx) { | |||
| auto out = internal::copy(formatting.prefix, ctx.out()); | |||
| std::size_t i = 0; | |||
| for (auto it = values.begin(), end = values.end(); it != end; ++it) { | |||
| if (i > 0) { | |||
| if (formatting.add_prepostfix_space) *out++ = ' '; | |||
| out = internal::copy(formatting.delimiter, out); | |||
| } | |||
| out = format_to(out, | |||
| internal::format_str_quoted( | |||
| (formatting.add_delimiter_spaces && i > 0), *it), | |||
| *it); | |||
| if (++i > formatting.range_length_limit) { | |||
| out = format_to(out, " ... <other elements>"); | |||
| break; | |||
| } | |||
| } | |||
| if (formatting.add_prepostfix_space) *out++ = ' '; | |||
| return internal::copy(formatting.postfix, out); | |||
| } | |||
| }; | |||
| FMT_END_NAMESPACE | |||
| #endif // FMT_RANGES_H_ | |||
| @@ -0,0 +1,293 @@ | |||
| /* | |||
| * For conversion between std::chrono::durations without undefined | |||
| * behaviour or erroneous results. | |||
| * This is a stripped down version of duration_cast, for inclusion in fmt. | |||
| * See https://github.com/pauldreik/safe_duration_cast | |||
| * | |||
| * Copyright Paul Dreik 2019 | |||
| * | |||
| * This file is licensed under the fmt license, see format.h | |||
| */ | |||
| #include <chrono> | |||
| #include <cmath> | |||
| #include <limits> | |||
| #include <type_traits> | |||
| #include "format.h" | |||
| FMT_BEGIN_NAMESPACE | |||
| namespace safe_duration_cast { | |||
| template <typename To, typename From, | |||
| FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||
| std::numeric_limits<From>::is_signed == | |||
| std::numeric_limits<To>::is_signed)> | |||
| FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||
| ec = 0; | |||
| using F = std::numeric_limits<From>; | |||
| using T = std::numeric_limits<To>; | |||
| static_assert(F::is_integer, "From must be integral"); | |||
| static_assert(T::is_integer, "To must be integral"); | |||
| // A and B are both signed, or both unsigned. | |||
| if (F::digits <= T::digits) { | |||
| // From fits in To without any problem. | |||
| } else { | |||
| // From does not always fit in To, resort to a dynamic check. | |||
| if (from < T::min() || from > T::max()) { | |||
| // outside range. | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| } | |||
| return static_cast<To>(from); | |||
| } | |||
| /** | |||
| * converts From to To, without loss. If the dynamic value of from | |||
| * can't be converted to To without loss, ec is set. | |||
| */ | |||
| template <typename To, typename From, | |||
| FMT_ENABLE_IF(!std::is_same<From, To>::value && | |||
| std::numeric_limits<From>::is_signed != | |||
| std::numeric_limits<To>::is_signed)> | |||
| FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||
| ec = 0; | |||
| using F = std::numeric_limits<From>; | |||
| using T = std::numeric_limits<To>; | |||
| static_assert(F::is_integer, "From must be integral"); | |||
| static_assert(T::is_integer, "To must be integral"); | |||
| if (F::is_signed && !T::is_signed) { | |||
| // From may be negative, not allowed! | |||
| if (from < 0) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| // From is positive. Can it always fit in To? | |||
| if (F::digits <= T::digits) { | |||
| // yes, From always fits in To. | |||
| } else { | |||
| // from may not fit in To, we have to do a dynamic check | |||
| if (from > static_cast<From>(T::max())) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| } | |||
| } | |||
| if (!F::is_signed && T::is_signed) { | |||
| // can from be held in To? | |||
| if (F::digits < T::digits) { | |||
| // yes, From always fits in To. | |||
| } else { | |||
| // from may not fit in To, we have to do a dynamic check | |||
| if (from > static_cast<From>(T::max())) { | |||
| // outside range. | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| } | |||
| } | |||
| // reaching here means all is ok for lossless conversion. | |||
| return static_cast<To>(from); | |||
| } // function | |||
| template <typename To, typename From, | |||
| FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||
| FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { | |||
| ec = 0; | |||
| return from; | |||
| } // function | |||
| // clang-format off | |||
| /** | |||
| * converts From to To if possible, otherwise ec is set. | |||
| * | |||
| * input | output | |||
| * ---------------------------------|--------------- | |||
| * NaN | NaN | |||
| * Inf | Inf | |||
| * normal, fits in output | converted (possibly lossy) | |||
| * normal, does not fit in output | ec is set | |||
| * subnormal | best effort | |||
| * -Inf | -Inf | |||
| */ | |||
| // clang-format on | |||
| template <typename To, typename From, | |||
| FMT_ENABLE_IF(!std::is_same<From, To>::value)> | |||
| FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||
| ec = 0; | |||
| using T = std::numeric_limits<To>; | |||
| static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||
| static_assert(std::is_floating_point<To>::value, "To must be floating"); | |||
| // catch the only happy case | |||
| if (std::isfinite(from)) { | |||
| if (from >= T::lowest() && from <= T::max()) { | |||
| return static_cast<To>(from); | |||
| } | |||
| // not within range. | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| // nan and inf will be preserved | |||
| return static_cast<To>(from); | |||
| } // function | |||
| template <typename To, typename From, | |||
| FMT_ENABLE_IF(std::is_same<From, To>::value)> | |||
| FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { | |||
| ec = 0; | |||
| static_assert(std::is_floating_point<From>::value, "From must be floating"); | |||
| return from; | |||
| } | |||
| /** | |||
| * safe duration cast between integral durations | |||
| */ | |||
| template <typename To, typename FromRep, typename FromPeriod, | |||
| FMT_ENABLE_IF(std::is_integral<FromRep>::value), | |||
| FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)> | |||
| To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||
| int& ec) { | |||
| using From = std::chrono::duration<FromRep, FromPeriod>; | |||
| ec = 0; | |||
| // the basic idea is that we need to convert from count() in the from type | |||
| // to count() in the To type, by multiplying it with this: | |||
| using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||
| static_assert(Factor::num > 0, "num must be positive"); | |||
| static_assert(Factor::den > 0, "den must be positive"); | |||
| // the conversion is like this: multiply from.count() with Factor::num | |||
| // /Factor::den and convert it to To::rep, all this without | |||
| // overflow/underflow. let's start by finding a suitable type that can hold | |||
| // both To, From and Factor::num | |||
| using IntermediateRep = | |||
| typename std::common_type<typename From::rep, typename To::rep, | |||
| decltype(Factor::num)>::type; | |||
| // safe conversion to IntermediateRep | |||
| IntermediateRep count = | |||
| lossless_integral_conversion<IntermediateRep>(from.count(), ec); | |||
| if (ec) { | |||
| return {}; | |||
| } | |||
| // multiply with Factor::num without overflow or underflow | |||
| if (Factor::num != 1) { | |||
| constexpr auto max1 = | |||
| std::numeric_limits<IntermediateRep>::max() / Factor::num; | |||
| if (count > max1) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| constexpr auto min1 = | |||
| std::numeric_limits<IntermediateRep>::min() / Factor::num; | |||
| if (count < min1) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| count *= Factor::num; | |||
| } | |||
| // this can't go wrong, right? den>0 is checked earlier. | |||
| if (Factor::den != 1) { | |||
| count /= Factor::den; | |||
| } | |||
| // convert to the to type, safely | |||
| using ToRep = typename To::rep; | |||
| const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec); | |||
| if (ec) { | |||
| return {}; | |||
| } | |||
| return To{tocount}; | |||
| } | |||
| /** | |||
| * safe duration_cast between floating point durations | |||
| */ | |||
| template <typename To, typename FromRep, typename FromPeriod, | |||
| FMT_ENABLE_IF(std::is_floating_point<FromRep>::value), | |||
| FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)> | |||
| To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from, | |||
| int& ec) { | |||
| using From = std::chrono::duration<FromRep, FromPeriod>; | |||
| ec = 0; | |||
| if (std::isnan(from.count())) { | |||
| // nan in, gives nan out. easy. | |||
| return To{std::numeric_limits<typename To::rep>::quiet_NaN()}; | |||
| } | |||
| // maybe we should also check if from is denormal, and decide what to do about | |||
| // it. | |||
| // +-inf should be preserved. | |||
| if (std::isinf(from.count())) { | |||
| return To{from.count()}; | |||
| } | |||
| // the basic idea is that we need to convert from count() in the from type | |||
| // to count() in the To type, by multiplying it with this: | |||
| using Factor = std::ratio_divide<typename From::period, typename To::period>; | |||
| static_assert(Factor::num > 0, "num must be positive"); | |||
| static_assert(Factor::den > 0, "den must be positive"); | |||
| // the conversion is like this: multiply from.count() with Factor::num | |||
| // /Factor::den and convert it to To::rep, all this without | |||
| // overflow/underflow. let's start by finding a suitable type that can hold | |||
| // both To, From and Factor::num | |||
| using IntermediateRep = | |||
| typename std::common_type<typename From::rep, typename To::rep, | |||
| decltype(Factor::num)>::type; | |||
| // force conversion of From::rep -> IntermediateRep to be safe, | |||
| // even if it will never happen be narrowing in this context. | |||
| IntermediateRep count = | |||
| safe_float_conversion<IntermediateRep>(from.count(), ec); | |||
| if (ec) { | |||
| return {}; | |||
| } | |||
| // multiply with Factor::num without overflow or underflow | |||
| if (Factor::num != 1) { | |||
| constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() / | |||
| static_cast<IntermediateRep>(Factor::num); | |||
| if (count > max1) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() / | |||
| static_cast<IntermediateRep>(Factor::num); | |||
| if (count < min1) { | |||
| ec = 1; | |||
| return {}; | |||
| } | |||
| count *= static_cast<IntermediateRep>(Factor::num); | |||
| } | |||
| // this can't go wrong, right? den>0 is checked earlier. | |||
| if (Factor::den != 1) { | |||
| using common_t = typename std::common_type<IntermediateRep, intmax_t>::type; | |||
| count /= static_cast<common_t>(Factor::den); | |||
| } | |||
| // convert to the to type, safely | |||
| using ToRep = typename To::rep; | |||
| const ToRep tocount = safe_float_conversion<ToRep>(count, ec); | |||
| if (ec) { | |||
| return {}; | |||
| } | |||
| return To{tocount}; | |||
| } | |||
| } // namespace safe_duration_cast | |||
| FMT_END_NAMESPACE | |||
| @@ -0,0 +1,27 @@ | |||
| // | |||
| // 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. | |||
| // | |||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #ifndef FMT_HEADER_ONLY | |||
| #define FMT_HEADER_ONLY | |||
| #endif | |||
| #endif | |||
| #ifndef FMT_USE_WINDOWS_H | |||
| #define FMT_USE_WINDOWS_H 0 | |||
| #endif | |||
| #include "bundled/core.h" | |||
| #include "bundled/format.h" | |||
| #else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib | |||
| #include "fmt/core.h" | |||
| #include "fmt/format.h" | |||
| #endif | |||
| @@ -0,0 +1,18 @@ | |||
| // | |||
| // Copyright(c) 2016 Gabi Melman. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| // | |||
| #pragma once | |||
| // | |||
| // include bundled or external copy of fmtlib's ostream support | |||
| // | |||
| #if !defined(SPDLOG_FMT_EXTERNAL) | |||
| #ifndef FMT_HEADER_ONLY | |||
| #define FMT_HEADER_ONLY | |||
| #endif | |||
| #include "bundled/ostream.h" | |||
| #include "fmt.h" | |||
| #else | |||
| #include <fmt/ostream.h> | |||
| #endif | |||
| @@ -0,0 +1,18 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "fmt/fmt.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| 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<formatter> clone() const = 0; | |||
| }; | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,245 @@ | |||
| // 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 "spdlog/logger.h" | |||
| #endif | |||
| #include "spdlog/sinks/sink.h" | |||
| #include "spdlog/details/backtracer.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #include <cstdio> | |||
| 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) | |||
| { | |||
| a.swap(b); | |||
| } | |||
| SPDLOG_INLINE bool logger::should_log(level::level_enum msg_level) const | |||
| { | |||
| return msg_level >= level_.load(std::memory_order_relaxed); | |||
| } | |||
| 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::level_enum>(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 seperate instance of the formatter object. | |||
| SPDLOG_INLINE void logger::set_formatter(std::unique_ptr<formatter> 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)); | |||
| } | |||
| 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<pattern_formatter>(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<level::level_enum>(flush_level_.load(std::memory_order_relaxed)); | |||
| } | |||
| // sinks | |||
| SPDLOG_INLINE const std::vector<sink_ptr> &logger::sinks() const | |||
| { | |||
| return sinks_; | |||
| } | |||
| SPDLOG_INLINE std::vector<sink_ptr> &logger::sinks() | |||
| { | |||
| return sinks_; | |||
| } | |||
| // error handler | |||
| SPDLOG_INLINE void logger::set_error_handler(err_handler handler) | |||
| { | |||
| custom_err_handler_ = handler; | |||
| } | |||
| // create new logger with same sinks and configuration. | |||
| SPDLOG_INLINE std::shared_ptr<logger> logger::clone(std::string logger_name) | |||
| { | |||
| auto cloned = std::make_shared<logger>(*this); | |||
| cloned->name_ = std::move(logger_name); | |||
| return cloned; | |||
| } | |||
| // protected methods | |||
| 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() | |||
| } | |||
| } | |||
| if (should_flush_(msg)) | |||
| { | |||
| flush_(); | |||
| } | |||
| } | |||
| SPDLOG_INLINE void logger::flush_() | |||
| { | |||
| for (auto &sink : sinks_) | |||
| { | |||
| SPDLOG_TRY | |||
| { | |||
| sink->flush(); | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| } | |||
| SPDLOG_INLINE void logger::dump_backtrace_() | |||
| { | |||
| using details::log_msg; | |||
| if (tracer_) | |||
| { | |||
| 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) | |||
| { | |||
| auto flush_level = flush_level_.load(std::memory_order_relaxed); | |||
| return (msg.level >= flush_level) && (msg.level != level::off); | |||
| } | |||
| SPDLOG_INLINE void logger::err_handler_(const std::string &msg) | |||
| { | |||
| 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<std::mutex> 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); | |||
| fprintf(stderr, "[*** LOG ERROR #%04zu ***] [%s] [%s] {%s}\n", err_counter, date_buf, name().c_str(), msg.c_str()); | |||
| } | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,382 @@ | |||
| // 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 "spdlog/common.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| #include "spdlog/details/backtracer.h" | |||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| #include "spdlog/details/os.h" | |||
| #endif | |||
| #include <vector> | |||
| #ifndef SPDLOG_NO_EXCEPTIONS | |||
| #define SPDLOG_LOGGER_CATCH() \ | |||
| catch (const std::exception &ex) \ | |||
| { \ | |||
| err_handler_(ex.what()); \ | |||
| } \ | |||
| catch (...) \ | |||
| { \ | |||
| err_handler_("Unknown exception in logger"); \ | |||
| } | |||
| #else | |||
| #define SPDLOG_LOGGER_CATCH() | |||
| #endif | |||
| namespace spdlog { | |||
| class logger | |||
| { | |||
| public: | |||
| // Empty logger | |||
| explicit logger(std::string name) | |||
| : name_(std::move(name)) | |||
| , sinks_() | |||
| {} | |||
| // Logger with range on sinks | |||
| template<typename It> | |||
| 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<typename... Args> | |||
| void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||
| { | |||
| auto level_enabled = should_log(lvl); | |||
| if (!level_enabled && !tracer_) | |||
| { | |||
| return; | |||
| } | |||
| SPDLOG_TRY | |||
| { | |||
| memory_buf_t buf; | |||
| fmt::format_to(buf, fmt, args...); | |||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||
| if (level_enabled) | |||
| { | |||
| sink_it_(log_msg); | |||
| } | |||
| if (tracer_) | |||
| { | |||
| tracer_.push_back(log_msg); | |||
| } | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| template<typename... Args> | |||
| void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||
| { | |||
| log(source_loc{}, lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void trace(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::trace, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void debug(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::debug, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void info(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::info, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void warn(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::warn, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void error(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::err, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void critical(string_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::critical, fmt, args...); | |||
| } | |||
| template<typename T> | |||
| void log(level::level_enum lvl, const T &msg) | |||
| { | |||
| log(source_loc{}, lvl, msg); | |||
| } | |||
| // T can be statically converted to string_view | |||
| template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, T>::type * = nullptr> | |||
| void log(source_loc loc, level::level_enum lvl, const T &msg) | |||
| { | |||
| auto level_enabled = should_log(lvl); | |||
| if (!level_enabled && !tracer_) | |||
| { | |||
| return; | |||
| } | |||
| SPDLOG_TRY | |||
| { | |||
| details::log_msg log_msg(loc, name_, lvl, msg); | |||
| if (level_enabled) | |||
| { | |||
| sink_it_(log_msg); | |||
| } | |||
| if (tracer_) | |||
| { | |||
| tracer_.push_back(log_msg); | |||
| } | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| void log(level::level_enum lvl, string_view_t msg) | |||
| { | |||
| log(source_loc{}, lvl, msg); | |||
| } | |||
| // T cannot be statically converted to string_view or wstring_view | |||
| template<class T, typename std::enable_if<!std::is_convertible<const T &, spdlog::string_view_t>::value && | |||
| !is_convertible_to_wstring_view<const T &>::value, | |||
| T>::type * = nullptr> | |||
| void log(source_loc loc, level::level_enum lvl, const T &msg) | |||
| { | |||
| log(loc, lvl, "{}", msg); | |||
| } | |||
| template<typename T> | |||
| void trace(const T &msg) | |||
| { | |||
| log(level::trace, msg); | |||
| } | |||
| template<typename T> | |||
| void debug(const T &msg) | |||
| { | |||
| log(level::debug, msg); | |||
| } | |||
| template<typename T> | |||
| void info(const T &msg) | |||
| { | |||
| log(level::info, msg); | |||
| } | |||
| template<typename T> | |||
| void warn(const T &msg) | |||
| { | |||
| log(level::warn, msg); | |||
| } | |||
| template<typename T> | |||
| void error(const T &msg) | |||
| { | |||
| log(level::err, msg); | |||
| } | |||
| template<typename T> | |||
| void critical(const T &msg) | |||
| { | |||
| log(level::critical, msg); | |||
| } | |||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| #ifndef _WIN32 | |||
| #error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows | |||
| #else | |||
| template<typename... Args> | |||
| void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||
| { | |||
| auto level_enabled = should_log(lvl); | |||
| if (!level_enabled && !tracer_) | |||
| { | |||
| return; | |||
| } | |||
| SPDLOG_TRY | |||
| { | |||
| // format to wmemory_buffer and convert to utf8 | |||
| fmt::wmemory_buffer wbuf; | |||
| fmt::format_to(wbuf, fmt, args...); | |||
| memory_buf_t buf; | |||
| details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); | |||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||
| if (level_enabled) | |||
| { | |||
| sink_it_(log_msg); | |||
| } | |||
| if (tracer_) | |||
| { | |||
| tracer_.push_back(log_msg); | |||
| } | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| template<typename... Args> | |||
| void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(source_loc{}, lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void trace(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::trace, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void debug(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::debug, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void info(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::info, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void warn(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::warn, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void error(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::err, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| void critical(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| log(level::critical, fmt, args...); | |||
| } | |||
| // T can be statically converted to wstring_view | |||
| template<class T, typename std::enable_if<is_convertible_to_wstring_view<const T &>::value, T>::type * = nullptr> | |||
| void log(source_loc loc, level::level_enum lvl, const T &msg) | |||
| { | |||
| if (!should_log(lvl)) | |||
| { | |||
| return; | |||
| } | |||
| try | |||
| { | |||
| memory_buf_t buf; | |||
| details::os::wstr_to_utf8buf(msg, buf); | |||
| details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); | |||
| sink_it_(log_msg); | |||
| } | |||
| SPDLOG_LOGGER_CATCH() | |||
| } | |||
| #endif // _WIN32 | |||
| #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| bool should_log(level::level_enum msg_level) const; | |||
| 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 seperate instance of the formatter object. | |||
| void set_formatter(std::unique_ptr<formatter> f); | |||
| 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<sink_ptr> &sinks() const; | |||
| std::vector<sink_ptr> &sinks(); | |||
| // error handler | |||
| void set_error_handler(err_handler); | |||
| // create new logger with same sinks and configuration. | |||
| virtual std::shared_ptr<logger> clone(std::string logger_name); | |||
| protected: | |||
| std::string name_; | |||
| std::vector<sink_ptr> sinks_; | |||
| spdlog::level_t level_{level::info}; | |||
| spdlog::level_t flush_level_{level::off}; | |||
| err_handler custom_err_handler_{nullptr}; | |||
| details::backtracer tracer_; | |||
| virtual void sink_it_(const details::log_msg &msg); | |||
| virtual void flush_(); | |||
| void dump_backtrace_(); | |||
| bool should_flush_(const details::log_msg &msg); | |||
| // 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); | |||
| }; | |||
| void swap(logger &a, logger &b); | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "logger-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,119 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #ifdef __ANDROID__ | |||
| #include "spdlog/details/fmt_helper.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/details/os.h" | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <android/log.h> | |||
| #include <chrono> | |||
| #include <mutex> | |||
| #include <string> | |||
| #include <thread> | |||
| #if !defined(SPDLOG_ANDROID_RETRIES) | |||
| #define SPDLOG_ANDROID_RETRIES 2 | |||
| #endif | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /* | |||
| * Android sink (logging using __android_log_write) | |||
| */ | |||
| template<typename Mutex> | |||
| class android_sink final : public base_sink<Mutex> | |||
| { | |||
| 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<Mutex>::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_write(priority, tag_.c_str(), msg_output); | |||
| int retry_count = 0; | |||
| while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) | |||
| { | |||
| details::os::sleep_for_millis(5); | |||
| ret = __android_log_write(priority, tag_.c_str(), msg_output); | |||
| retry_count++; | |||
| } | |||
| if (ret < 0) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("__android_log_write() failed", ret)); | |||
| } | |||
| } | |||
| void flush_() override {} | |||
| private: | |||
| 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<std::mutex>; | |||
| using android_sink_st = android_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // Create and register android syslog logger | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") | |||
| { | |||
| return Factory::template create<sinks::android_sink_mt>(logger_name, tag); | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") | |||
| { | |||
| return Factory::template create<sinks::android_sink_st>(logger_name, tag); | |||
| } | |||
| } // namespace spdlog | |||
| #endif // __ANDROID__ | |||
| @@ -0,0 +1,136 @@ | |||
| // 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 "spdlog/sinks/ansicolor_sink.h" | |||
| #endif | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #include "spdlog/details/os.h" | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE ansicolor_sink<ConsoleMutex>::ansicolor_sink(FILE *target_file, color_mode mode) | |||
| : target_file_(target_file) | |||
| , mutex_(ConsoleMutex::mutex()) | |||
| , formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||
| { | |||
| set_color_mode(mode); | |||
| colors_[level::trace] = white; | |||
| colors_[level::debug] = cyan; | |||
| colors_[level::info] = green; | |||
| colors_[level::warn] = yellow_bold; | |||
| colors_[level::err] = red_bold; | |||
| colors_[level::critical] = bold_on_red; | |||
| colors_[level::off] = reset; | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| colors_[color_level] = color; | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::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<mutex_t> lock(mutex_); | |||
| 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_[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<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::flush() | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| fflush(target_file_); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::move(sink_formatter); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE bool ansicolor_sink<ConsoleMutex>::should_color() | |||
| { | |||
| return should_do_colors_; | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::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; | |||
| } | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) | |||
| { | |||
| fwrite(color_code.data(), sizeof(string_view_t::char_type), color_code.size(), target_file_); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||
| { | |||
| fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); | |||
| } | |||
| // ansicolor_stdout_sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE ansicolor_stdout_sink<ConsoleMutex>::ansicolor_stdout_sink(color_mode mode) | |||
| : ansicolor_sink<ConsoleMutex>(stdout, mode) | |||
| {} | |||
| // ansicolor_stderr_sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE ansicolor_stderr_sink<ConsoleMutex>::ansicolor_stderr_sink(color_mode mode) | |||
| : ansicolor_sink<ConsoleMutex>(stderr, mode) | |||
| {} | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,113 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/console_globals.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/sinks/sink.h" | |||
| #include <memory> | |||
| #include <mutex> | |||
| #include <string> | |||
| #include <unordered_map> | |||
| 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<typename ConsoleMutex> | |||
| 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 &operator=(const 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(); | |||
| void log(const details::log_msg &msg) override; | |||
| void flush() override; | |||
| void set_pattern(const std::string &pattern) final; | |||
| void set_formatter(std::unique_ptr<spdlog::formatter> 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"; | |||
| private: | |||
| FILE *target_file_; | |||
| mutex_t &mutex_; | |||
| bool should_do_colors_; | |||
| std::unique_ptr<spdlog::formatter> formatter_; | |||
| std::unordered_map<level::level_enum, string_view_t, level::level_hasher> colors_; | |||
| void print_ccode_(const string_view_t &color_code); | |||
| void print_range_(const memory_buf_t &formatted, size_t start, size_t end); | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class ansicolor_stdout_sink : public ansicolor_sink<ConsoleMutex> | |||
| { | |||
| public: | |||
| explicit ansicolor_stdout_sink(color_mode mode = color_mode::automatic); | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class ansicolor_stderr_sink : public ansicolor_sink<ConsoleMutex> | |||
| { | |||
| public: | |||
| explicit ansicolor_stderr_sink(color_mode mode = color_mode::automatic); | |||
| }; | |||
| using ansicolor_stdout_sink_mt = ansicolor_stdout_sink<details::console_mutex>; | |||
| using ansicolor_stdout_sink_st = ansicolor_stdout_sink<details::console_nullmutex>; | |||
| using ansicolor_stderr_sink_mt = ansicolor_stderr_sink<details::console_mutex>; | |||
| using ansicolor_stderr_sink_st = ansicolor_stderr_sink<details::console_nullmutex>; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "ansicolor_sink-inl.h" | |||
| #endif | |||
| @@ -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 "spdlog/sinks/base_sink.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #include <memory> | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink() | |||
| : formatter_{details::make_unique<spdlog::pattern_formatter>()} | |||
| {} | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::base_sink(std::unique_ptr<spdlog::formatter> formatter) | |||
| : formatter_{std::move(formatter)} | |||
| {} | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::log(const details::log_msg &msg) | |||
| { | |||
| std::lock_guard<Mutex> lock(mutex_); | |||
| sink_it_(msg); | |||
| } | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::flush() | |||
| { | |||
| std::lock_guard<Mutex> lock(mutex_); | |||
| flush_(); | |||
| } | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern(const std::string &pattern) | |||
| { | |||
| std::lock_guard<Mutex> lock(mutex_); | |||
| set_pattern_(pattern); | |||
| } | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||
| { | |||
| std::lock_guard<Mutex> lock(mutex_); | |||
| set_formatter_(std::move(sink_formatter)); | |||
| } | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_pattern_(const std::string &pattern) | |||
| { | |||
| set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||
| } | |||
| template<typename Mutex> | |||
| void SPDLOG_INLINE spdlog::sinks::base_sink<Mutex>::set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) | |||
| { | |||
| formatter_ = std::move(sink_formatter); | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| // 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 "spdlog/common.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| #include "spdlog/sinks/sink.h" | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| class base_sink : public sink | |||
| { | |||
| public: | |||
| base_sink(); | |||
| explicit base_sink(std::unique_ptr<spdlog::formatter> formatter); | |||
| base_sink(const base_sink &) = delete; | |||
| base_sink &operator=(const base_sink &) = delete; | |||
| void log(const details::log_msg &msg) final; | |||
| void flush() final; | |||
| void set_pattern(const std::string &pattern) final; | |||
| void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final; | |||
| protected: | |||
| // sink formatter | |||
| std::unique_ptr<spdlog::formatter> 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<spdlog::formatter> sink_formatter); | |||
| }; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "base_sink-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,43 @@ | |||
| // 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 "spdlog/sinks/basic_file_sink.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/os.h" | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t &filename, bool truncate) | |||
| { | |||
| file_helper_.open(filename, truncate); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE const filename_t &basic_file_sink<Mutex>::filename() const | |||
| { | |||
| return file_helper_.filename(); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||
| { | |||
| memory_buf_t formatted; | |||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||
| file_helper_.write(formatted); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE void basic_file_sink<Mutex>::flush_() | |||
| { | |||
| file_helper_.flush(); | |||
| } | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,58 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/file_helper.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <mutex> | |||
| #include <string> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /* | |||
| * Trivial file sink with single file as target | |||
| */ | |||
| template<typename Mutex> | |||
| class basic_file_sink final : public base_sink<Mutex> | |||
| { | |||
| public: | |||
| explicit basic_file_sink(const filename_t &filename, bool truncate = false); | |||
| const filename_t &filename() const; | |||
| 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<std::mutex>; | |||
| using basic_file_sink_st = basic_file_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // | |||
| // factory functions | |||
| // | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||
| { | |||
| return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) | |||
| { | |||
| return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); | |||
| } | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "basic_file_sink-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,185 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/file_helper.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/fmt/fmt.h" | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/os.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <chrono> | |||
| #include <cstdio> | |||
| #include <ctime> | |||
| #include <mutex> | |||
| #include <string> | |||
| 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::format( | |||
| SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); | |||
| } | |||
| }; | |||
| /* | |||
| * 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. | |||
| */ | |||
| template<typename Mutex, typename FileNameCalc = daily_filename_calculator> | |||
| class daily_file_sink final : public base_sink<Mutex> | |||
| { | |||
| 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) | |||
| : base_filename_(std::move(base_filename)) | |||
| , rotation_h_(rotation_hour) | |||
| , rotation_m_(rotation_minute) | |||
| , truncate_(truncate) | |||
| , max_files_(max_files) | |||
| , filenames_q_() | |||
| { | |||
| if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("daily_file_sink: Invalid rotation time in ctor")); | |||
| } | |||
| auto now = log_clock::now(); | |||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now)); | |||
| file_helper_.open(filename, truncate_); | |||
| rotation_tp_ = next_rotation_tp_(); | |||
| if (max_files_ > 0) | |||
| { | |||
| filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_)); | |||
| filenames_q_.push_back(std::move(filename)); | |||
| } | |||
| } | |||
| const filename_t &filename() const | |||
| { | |||
| return file_helper_.filename(); | |||
| } | |||
| protected: | |||
| void sink_it_(const details::log_msg &msg) override | |||
| { | |||
| #ifdef SPDLOG_NO_DATETIME | |||
| auto time = log_clock::now(); | |||
| #else | |||
| auto time = msg.time; | |||
| #endif | |||
| bool should_rotate = time >= rotation_tp_; | |||
| if (should_rotate) | |||
| { | |||
| auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time)); | |||
| file_helper_.open(filename, truncate_); | |||
| rotation_tp_ = next_rotation_tp_(); | |||
| } | |||
| memory_buf_t formatted; | |||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||
| file_helper_.write(formatted); | |||
| // Do the cleaning ony at the end because it might throw on failure. | |||
| if (should_rotate && max_files_ > 0) | |||
| { | |||
| delete_old_(); | |||
| } | |||
| } | |||
| void flush_() override | |||
| { | |||
| file_helper_.flush(); | |||
| } | |||
| private: | |||
| 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 = 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 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<filename_t> filenames_q_; | |||
| }; | |||
| using daily_file_sink_mt = daily_file_sink<std::mutex>; | |||
| using daily_file_sink_st = daily_file_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // | |||
| // factory functions | |||
| // | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> daily_logger_mt( | |||
| const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||
| { | |||
| return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> daily_logger_st( | |||
| const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) | |||
| { | |||
| return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -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 "base_sink.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #include <algorithm> | |||
| #include <memory> | |||
| #include <mutex> | |||
| #include <vector> | |||
| // Distribution sink (mux). Stores a vector of sinks which get called when log | |||
| // is called | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| class dist_sink : public base_sink<Mutex> | |||
| { | |||
| public: | |||
| dist_sink() = default; | |||
| dist_sink(const dist_sink &) = delete; | |||
| dist_sink &operator=(const dist_sink &) = delete; | |||
| void add_sink(std::shared_ptr<sink> sink) | |||
| { | |||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||
| sinks_.push_back(sink); | |||
| } | |||
| void remove_sink(std::shared_ptr<sink> sink) | |||
| { | |||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||
| sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); | |||
| } | |||
| void set_sinks(std::vector<std::shared_ptr<sink>> sinks) | |||
| { | |||
| std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); | |||
| sinks_ = std::move(sinks); | |||
| } | |||
| std::vector<std::shared_ptr<sink>> &sinks() | |||
| { | |||
| return sinks_; | |||
| } | |||
| protected: | |||
| void sink_it_(const details::log_msg &msg) override | |||
| { | |||
| for (auto &sink : sinks_) | |||
| { | |||
| if (sink->should_log(msg.level)) | |||
| { | |||
| sink->log(msg); | |||
| } | |||
| } | |||
| } | |||
| void flush_() override | |||
| { | |||
| for (auto &sink : sinks_) | |||
| { | |||
| sink->flush(); | |||
| } | |||
| } | |||
| void set_pattern_(const std::string &pattern) override | |||
| { | |||
| set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); | |||
| } | |||
| void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override | |||
| { | |||
| base_sink<Mutex>::formatter_ = std::move(sink_formatter); | |||
| for (auto &sink : sinks_) | |||
| { | |||
| sink->set_formatter(base_sink<Mutex>::formatter_->clone()); | |||
| } | |||
| } | |||
| std::vector<std::shared_ptr<sink>> sinks_; | |||
| }; | |||
| using dist_sink_mt = dist_sink<std::mutex>; | |||
| using dist_sink_st = dist_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,94 @@ | |||
| // 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 "spdlog/details/null_mutex.h" | |||
| #include "spdlog/details/log_msg.h" | |||
| #include <mutex> | |||
| #include <string> | |||
| #include <chrono> | |||
| // Duplicate message removal sink. | |||
| // Skip the message if previous one is identical and less than "max_skip_duration" have passed | |||
| // | |||
| // Example: | |||
| // | |||
| // #include "spdlog/sinks/dup_filter_sink.h" | |||
| // | |||
| // int main() { | |||
| // auto dup_filter = std::make_shared<dup_filter_sink_st>(std::chrono::seconds(5)); | |||
| // dup_filter->add_sink(std::make_shared<stdout_color_sink_mt>()); | |||
| // 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 | |||
| #ifdef SPDLOG_NO_DATETIME | |||
| #error "spdlog::sinks::dup_filter_sink: cannot work when SPDLOG_NO_DATETIME is defined" | |||
| #endif | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| class dup_filter_sink : public dist_sink<Mutex> | |||
| { | |||
| public: | |||
| template<class Rep, class Period> | |||
| explicit dup_filter_sink(std::chrono::duration<Rep, Period> 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; | |||
| void sink_it_(const details::log_msg &msg) override | |||
| { | |||
| bool filtered = filter_(msg); | |||
| if (!filtered) | |||
| { | |||
| skip_counter_ += 1; | |||
| return; | |||
| } | |||
| // log the "skipped.." message | |||
| if (skip_counter_ > 0) | |||
| { | |||
| memory_buf_t buf; | |||
| fmt::format_to(buf, "Skipped {} duplicate messages..", skip_counter_); | |||
| details::log_msg skipped_msg{msg.logger_name, msg.level, string_view_t{buf.data(), buf.size()}}; | |||
| dist_sink<Mutex>::sink_it_(skipped_msg); | |||
| } | |||
| // log current message | |||
| dist_sink<Mutex>::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) | |||
| { | |||
| 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<std::mutex>; | |||
| using dup_filter_sink_st = dup_filter_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,48 @@ | |||
| // Copyright(c) 2016 Alexander Dalshov. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #if defined(_WIN32) | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include <winbase.h> | |||
| #include <mutex> | |||
| #include <string> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /* | |||
| * MSVC sink (logging using OutputDebugStringA) | |||
| */ | |||
| template<typename Mutex> | |||
| class msvc_sink : public base_sink<Mutex> | |||
| { | |||
| public: | |||
| explicit msvc_sink() {} | |||
| protected: | |||
| void sink_it_(const details::log_msg &msg) override | |||
| { | |||
| memory_buf_t formatted; | |||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||
| OutputDebugStringA(fmt::to_string(formatted).c_str()); | |||
| } | |||
| void flush_() override {} | |||
| }; | |||
| using msvc_sink_mt = msvc_sink<std::mutex>; | |||
| using msvc_sink_st = msvc_sink<details::null_mutex>; | |||
| using windebug_sink_mt = msvc_sink_mt; | |||
| using windebug_sink_st = msvc_sink_st; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| #endif | |||
| @@ -0,0 +1,44 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // 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/details/synchronous_factory.h" | |||
| #include <mutex> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| class null_sink : public base_sink<Mutex> | |||
| { | |||
| protected: | |||
| void sink_it_(const details::log_msg &) override {} | |||
| void flush_() override {} | |||
| }; | |||
| using null_sink_mt = null_sink<details::null_mutex>; | |||
| using null_sink_st = null_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) | |||
| { | |||
| auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); | |||
| null_logger->set_level(level::off); | |||
| return null_logger; | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) | |||
| { | |||
| auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); | |||
| null_logger->set_level(level::off); | |||
| return null_logger; | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,50 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // 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 <mutex> | |||
| #include <ostream> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| class ostream_sink final : public base_sink<Mutex> | |||
| { | |||
| 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<Mutex>::formatter_->format(msg, formatted); | |||
| ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); | |||
| if (force_flush_) | |||
| { | |||
| ostream_.flush(); | |||
| } | |||
| } | |||
| void flush_() override | |||
| { | |||
| ostream_.flush(); | |||
| } | |||
| std::ostream &ostream_; | |||
| bool force_flush_; | |||
| }; | |||
| using ostream_sink_mt = ostream_sink<std::mutex>; | |||
| using ostream_sink_st = ostream_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,130 @@ | |||
| // 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 "spdlog/sinks/rotating_file_sink.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/file_helper.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/fmt/fmt.h" | |||
| #include <cerrno> | |||
| #include <chrono> | |||
| #include <ctime> | |||
| #include <mutex> | |||
| #include <string> | |||
| #include <tuple> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( | |||
| filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open) | |||
| : base_filename_(std::move(base_filename)) | |||
| , max_size_(max_size) | |||
| , max_files_(max_files) | |||
| { | |||
| 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_(); | |||
| } | |||
| } | |||
| // calc filename according to index and file extension if exists. | |||
| // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, std::size_t index) | |||
| { | |||
| if (index == 0u) | |||
| { | |||
| return filename; | |||
| } | |||
| filename_t basename, ext; | |||
| std::tie(basename, ext) = details::file_helper::split_by_extension(filename); | |||
| return fmt::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE const filename_t &rotating_file_sink<Mutex>::filename() const | |||
| { | |||
| return file_helper_.filename(); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) | |||
| { | |||
| memory_buf_t formatted; | |||
| base_sink<Mutex>::formatter_->format(msg, formatted); | |||
| current_size_ += formatted.size(); | |||
| if (current_size_ > max_size_) | |||
| { | |||
| rotate_(); | |||
| current_size_ = formatted.size(); | |||
| } | |||
| file_helper_.write(formatted); | |||
| } | |||
| template<typename Mutex> | |||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::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<typename Mutex> | |||
| SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() | |||
| { | |||
| using details::os::filename_to_str; | |||
| file_helper_.close(); | |||
| for (auto i = max_files_; i > 0; --i) | |||
| { | |||
| filename_t src = calc_filename(base_filename_, i - 1); | |||
| if (!details::file_helper::file_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; | |||
| SPDLOG_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<typename Mutex> | |||
| SPDLOG_INLINE bool rotating_file_sink<Mutex>::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 | |||
| @@ -0,0 +1,78 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/file_helper.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <chrono> | |||
| #include <mutex> | |||
| #include <string> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| // | |||
| // Rotating file sink based on size | |||
| // | |||
| template<typename Mutex> | |||
| class rotating_file_sink final : public base_sink<Mutex> | |||
| { | |||
| public: | |||
| rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false); | |||
| static filename_t calc_filename(const filename_t &filename, std::size_t index); | |||
| const filename_t &filename() const; | |||
| 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<std::mutex>; | |||
| using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // | |||
| // factory functions | |||
| // | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> 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) | |||
| { | |||
| return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> 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) | |||
| { | |||
| return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files, rotate_on_open); | |||
| } | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "rotating_file_sink-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,25 @@ | |||
| // 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 "spdlog/sinks/sink.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| 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<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); | |||
| } | |||
| @@ -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 "spdlog/details/log_msg.h" | |||
| #include "spdlog/formatter.h" | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| class 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<spdlog::formatter> 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 | |||
| @@ -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 "spdlog/sinks/stdout_color_sinks.h" | |||
| #endif | |||
| #include "spdlog/logger.h" | |||
| #include "spdlog/common.h" | |||
| namespace spdlog { | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode) | |||
| { | |||
| return Factory::template create<sinks::stdout_color_sink_mt>(logger_name, mode); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode) | |||
| { | |||
| return Factory::template create<sinks::stdout_color_sink_st>(logger_name, mode); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode) | |||
| { | |||
| return Factory::template create<sinks::stderr_color_sink_mt>(logger_name, mode); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_color_st(const std::string &logger_name, color_mode mode) | |||
| { | |||
| return Factory::template create<sinks::stderr_color_sink_st>(logger_name, mode); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,45 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #ifdef _WIN32 | |||
| #include "spdlog/sinks/wincolor_sink.h" | |||
| #else | |||
| #include "spdlog/sinks/ansicolor_sink.h" | |||
| #endif | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| 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<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stdout_color_st(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name, color_mode mode = color_mode::automatic); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> 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 | |||
| @@ -0,0 +1,94 @@ | |||
| // 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 "spdlog/sinks/stdout_sinks.h" | |||
| #endif | |||
| #include "spdlog/details/console_globals.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| #include <memory> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file) | |||
| : mutex_(ConsoleMutex::mutex()) | |||
| , file_(file) | |||
| , formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||
| {} | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &msg) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| memory_buf_t formatted; | |||
| formatter_->format(msg, formatted); | |||
| fwrite(formatted.data(), sizeof(char), formatted.size(), file_); | |||
| fflush(file_); // flush every line to terminal | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::flush() | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| fflush(file_); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_pattern(const std::string &pattern) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::move(sink_formatter); | |||
| } | |||
| // stdout sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE stdout_sink<ConsoleMutex>::stdout_sink() | |||
| : stdout_sink_base<ConsoleMutex>(stdout) | |||
| {} | |||
| // stderr sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE stderr_sink<ConsoleMutex>::stderr_sink() | |||
| : stdout_sink_base<ConsoleMutex>(stderr) | |||
| {} | |||
| } // namespace sinks | |||
| // factory methods | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::stdout_sink_mt>(logger_name); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::stdout_sink_st>(logger_name); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::stderr_sink_mt>(logger_name); | |||
| } | |||
| template<typename Factory> | |||
| SPDLOG_INLINE std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::stderr_sink_st>(logger_name); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,76 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/details/console_globals.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include "spdlog/sinks/sink.h" | |||
| #include <cstdio> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename ConsoleMutex> | |||
| 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 &operator=(const 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<spdlog::formatter> sink_formatter) override; | |||
| protected: | |||
| mutex_t &mutex_; | |||
| FILE *file_; | |||
| std::unique_ptr<spdlog::formatter> formatter_; | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class stdout_sink : public stdout_sink_base<ConsoleMutex> | |||
| { | |||
| public: | |||
| stdout_sink(); | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class stderr_sink : public stdout_sink_base<ConsoleMutex> | |||
| { | |||
| public: | |||
| stderr_sink(); | |||
| }; | |||
| using stdout_sink_mt = stdout_sink<details::console_mutex>; | |||
| using stdout_sink_st = stdout_sink<details::console_nullmutex>; | |||
| using stderr_sink_mt = stderr_sink<details::console_mutex>; | |||
| using stderr_sink_st = stderr_sink<details::console_nullmutex>; | |||
| } // namespace sinks | |||
| // factory methods | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name); | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name); | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "stdout_sinks-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,108 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include <array> | |||
| #include <string> | |||
| #include <syslog.h> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /** | |||
| * Sink that write to syslog using the `syscall()` library call. | |||
| */ | |||
| template<typename Mutex> | |||
| class syslog_sink : public base_sink<Mutex> | |||
| { | |||
| 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<Mutex>::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<size_t>(std::numeric_limits<int>::max())) | |||
| { | |||
| length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||
| } | |||
| ::syslog(syslog_prio_from_level(msg), "%.*s", static_cast<int>(length), payload.data()); | |||
| } | |||
| void flush_() override {} | |||
| bool enable_formatting_ = false; | |||
| private: | |||
| using levels_array = std::array<int, 7>; | |||
| levels_array syslog_levels_; | |||
| // must store the ident because the man says openlog might use the pointer as | |||
| // is and not a string copy | |||
| const std::string ident_; | |||
| // | |||
| // Simply maps spdlog's log level to syslog priority level. | |||
| // | |||
| int syslog_prio_from_level(const details::log_msg &msg) const | |||
| { | |||
| return syslog_levels_.at(static_cast<levels_array::size_type>(msg.level)); | |||
| } | |||
| }; | |||
| using syslog_sink_mt = syslog_sink<std::mutex>; | |||
| using syslog_sink_st = syslog_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // Create and register a syslog logger | |||
| template<typename Factory = default_factory> | |||
| inline std::shared_ptr<logger> 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<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||
| } | |||
| template<typename Factory = default_factory> | |||
| inline std::shared_ptr<logger> 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<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility, enable_formatting); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,98 @@ | |||
| // Copyright(c) 2019 ZVYAGIN.Alexander@gmail.com | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/sinks/base_sink.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <systemd/sd-journal.h> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /** | |||
| * Sink that write to systemd journal using the `sd_journal_send()` library call. | |||
| * | |||
| * Locking is not needed, as `sd_journal_send()` itself is thread-safe. | |||
| */ | |||
| template<typename Mutex> | |||
| class systemd_sink : public base_sink<Mutex> | |||
| { | |||
| public: | |||
| // | |||
| systemd_sink() | |||
| : 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: | |||
| using levels_array = std::array<int, 7>; | |||
| levels_array syslog_levels_; | |||
| void sink_it_(const details::log_msg &msg) override | |||
| { | |||
| int err; | |||
| size_t length = msg.payload.size(); | |||
| // limit to max int | |||
| if (length > static_cast<size_t>(std::numeric_limits<int>::max())) | |||
| { | |||
| length = static_cast<size_t>(std::numeric_limits<int>::max()); | |||
| } | |||
| // 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<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), nullptr); | |||
| } | |||
| else | |||
| { | |||
| err = (sd_journal_send)("MESSAGE=%.*s", static_cast<int>(length), msg.payload.data(), "PRIORITY=%d", syslog_level(msg.level), | |||
| "SOURCE_FILE=%s", msg.source.filename, "SOURCE_LINE=%d", msg.source.line, "SOURCE_FUNC=%s", msg.source.funcname, nullptr); | |||
| } | |||
| if (err) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("Failed writing to systemd", errno)); | |||
| } | |||
| } | |||
| int syslog_level(level::level_enum l) | |||
| { | |||
| return syslog_levels_.at(static_cast<levels_array::size_type>(l)); | |||
| } | |||
| void flush_() override {} | |||
| }; | |||
| using systemd_sink_mt = systemd_sink<std::mutex>; | |||
| using systemd_sink_st = systemd_sink<details::null_mutex>; | |||
| } // namespace sinks | |||
| // Create and register a syslog logger | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> systemd_logger_mt(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::systemd_sink_mt>(logger_name); | |||
| } | |||
| template<typename Factory = spdlog::synchronous_factory> | |||
| inline std::shared_ptr<logger> systemd_logger_st(const std::string &logger_name) | |||
| { | |||
| return Factory::template create<sinks::systemd_sink_st>(logger_name); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -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 "spdlog/sinks/wincolor_sink.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE wincolor_sink<ConsoleMutex>::wincolor_sink(HANDLE out_handle, color_mode mode) | |||
| : out_handle_(out_handle) | |||
| , mutex_(ConsoleMutex::mutex()) | |||
| , formatter_(details::make_unique<spdlog::pattern_formatter>()) | |||
| { | |||
| // check if out_handle is points to the actual console. | |||
| // ::GetConsoleMode() should return 0 if it is redirected or not valid console handle. | |||
| DWORD console_mode; | |||
| in_console_ = ::GetConsoleMode(out_handle, &console_mode) != 0; | |||
| set_color_mode(mode); | |||
| colors_[level::trace] = WHITE; | |||
| colors_[level::debug] = CYAN; | |||
| colors_[level::info] = GREEN; | |||
| colors_[level::warn] = YELLOW | BOLD; | |||
| colors_[level::err] = RED | BOLD; // red bold | |||
| colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background | |||
| colors_[level::off] = 0; | |||
| } | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE wincolor_sink<ConsoleMutex>::~wincolor_sink() | |||
| { | |||
| this->flush(); | |||
| } | |||
| // change the color for the given level | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color(level::level_enum level, WORD color) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| colors_[level] = color; | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::log(const details::log_msg &msg) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| memory_buf_t formatted; | |||
| formatter_->format(msg, formatted); | |||
| if (!in_console_) | |||
| { | |||
| write_to_file_(formatted); | |||
| return; | |||
| } | |||
| if (should_do_colors_ && msg.color_range_end > msg.color_range_start) | |||
| { | |||
| // before color range | |||
| print_range_(formatted, 0, msg.color_range_start); | |||
| // in color range | |||
| auto orig_attribs = set_foreground_color_(colors_[msg.level]); | |||
| print_range_(formatted, msg.color_range_start, msg.color_range_end); | |||
| // reset to orig colors | |||
| ::SetConsoleTextAttribute(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) | |||
| { | |||
| print_range_(formatted, 0, formatted.size()); | |||
| } | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::flush() | |||
| { | |||
| // windows console always flushed? | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_pattern(const std::string &pattern) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) | |||
| { | |||
| std::lock_guard<mutex_t> lock(mutex_); | |||
| formatter_ = std::move(sink_formatter); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_color_mode(color_mode mode) | |||
| { | |||
| switch (mode) | |||
| { | |||
| case color_mode::always: | |||
| case color_mode::automatic: | |||
| should_do_colors_ = true; | |||
| break; | |||
| case color_mode::never: | |||
| should_do_colors_ = false; | |||
| break; | |||
| default: | |||
| should_do_colors_ = true; | |||
| } | |||
| } | |||
| // set foreground color and return the orig console attributes (for resetting later) | |||
| template<typename ConsoleMutex> | |||
| WORD SPDLOG_INLINE wincolor_sink<ConsoleMutex>::set_foreground_color_(WORD attribs) | |||
| { | |||
| CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; | |||
| ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); | |||
| WORD back_color = orig_buffer_info.wAttributes; | |||
| // retrieve the current background color | |||
| back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); | |||
| // keep the background color unchanged | |||
| ::SetConsoleTextAttribute(out_handle_, attribs | back_color); | |||
| return orig_buffer_info.wAttributes; // return orig attribs | |||
| } | |||
| // print a range of formatted message to console | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t &formatted, size_t start, size_t end) | |||
| { | |||
| auto size = static_cast<DWORD>(end - start); | |||
| ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); | |||
| } | |||
| template<typename ConsoleMutex> | |||
| void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::write_to_file_(const memory_buf_t &formatted) | |||
| { | |||
| if (out_handle_ == nullptr) // no console and no file redirect | |||
| { | |||
| return; | |||
| } | |||
| auto size = static_cast<DWORD>(formatted.size()); | |||
| if (size == 0) | |||
| { | |||
| return; | |||
| } | |||
| DWORD total_written = 0; | |||
| do | |||
| { | |||
| DWORD bytes_written = 0; | |||
| bool ok = ::WriteFile(out_handle_, formatted.data() + total_written, size - total_written, &bytes_written, nullptr) != 0; | |||
| if (!ok || bytes_written == 0) | |||
| { | |||
| SPDLOG_THROW(spdlog_ex("wincolor_sink: write_to_file_ failed. GetLastError(): " + std::to_string(::GetLastError()))); | |||
| } | |||
| total_written += bytes_written; | |||
| } while (total_written < size); | |||
| } | |||
| // wincolor_stdout_sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE wincolor_stdout_sink<ConsoleMutex>::wincolor_stdout_sink(color_mode mode) | |||
| : wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_OUTPUT_HANDLE), mode) | |||
| {} | |||
| // wincolor_stderr_sink | |||
| template<typename ConsoleMutex> | |||
| SPDLOG_INLINE wincolor_stderr_sink<ConsoleMutex>::wincolor_stderr_sink(color_mode mode) | |||
| : wincolor_sink<ConsoleMutex>(::GetStdHandle(STD_ERROR_HANDLE), mode) | |||
| {} | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,92 @@ | |||
| // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. | |||
| // Distributed under the MIT License (http://opensource.org/licenses/MIT) | |||
| #pragma once | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/console_globals.h" | |||
| #include "spdlog/details/null_mutex.h" | |||
| #include "spdlog/sinks/sink.h" | |||
| #include <memory> | |||
| #include <mutex> | |||
| #include <string> | |||
| #include <unordered_map> | |||
| #include <wincon.h> | |||
| namespace spdlog { | |||
| namespace sinks { | |||
| /* | |||
| * Windows color console sink. Uses WriteConsoleA to write to the console with | |||
| * colors | |||
| */ | |||
| template<typename ConsoleMutex> | |||
| class wincolor_sink : public sink | |||
| { | |||
| public: | |||
| const WORD BOLD = FOREGROUND_INTENSITY; | |||
| const WORD RED = FOREGROUND_RED; | |||
| const WORD GREEN = FOREGROUND_GREEN; | |||
| const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; | |||
| const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; | |||
| const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; | |||
| wincolor_sink(HANDLE out_handle, color_mode mode); | |||
| ~wincolor_sink() override; | |||
| wincolor_sink(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, WORD color); | |||
| void log(const details::log_msg &msg) final override; | |||
| void flush() final override; | |||
| void set_pattern(const std::string &pattern) override final; | |||
| void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final; | |||
| void set_color_mode(color_mode mode); | |||
| protected: | |||
| using mutex_t = typename ConsoleMutex::mutex_t; | |||
| HANDLE out_handle_; | |||
| mutex_t &mutex_; | |||
| bool in_console_; | |||
| bool should_do_colors_; | |||
| std::unique_ptr<spdlog::formatter> formatter_; | |||
| std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; | |||
| // set foreground color and return the orig console attributes (for resetting later) | |||
| WORD set_foreground_color_(WORD 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); | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class wincolor_stdout_sink : public wincolor_sink<ConsoleMutex> | |||
| { | |||
| public: | |||
| explicit wincolor_stdout_sink(color_mode mode = color_mode::automatic); | |||
| }; | |||
| template<typename ConsoleMutex> | |||
| class wincolor_stderr_sink : public wincolor_sink<ConsoleMutex> | |||
| { | |||
| public: | |||
| explicit wincolor_stderr_sink(color_mode mode = color_mode::automatic); | |||
| }; | |||
| using wincolor_stdout_sink_mt = wincolor_stdout_sink<details::console_mutex>; | |||
| using wincolor_stdout_sink_st = wincolor_stdout_sink<details::console_nullmutex>; | |||
| using wincolor_stderr_sink_mt = wincolor_stderr_sink<details::console_mutex>; | |||
| using wincolor_stderr_sink_st = wincolor_stderr_sink<details::console_nullmutex>; | |||
| } // namespace sinks | |||
| } // namespace spdlog | |||
| #ifdef SPDLOG_HEADER_ONLY | |||
| #include "wincolor_sink-inl.h" | |||
| #endif | |||
| @@ -0,0 +1,115 @@ | |||
| // 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 "spdlog/spdlog.h" | |||
| #endif | |||
| #include "spdlog/common.h" | |||
| #include "spdlog/details/pattern_formatter.h" | |||
| namespace spdlog { | |||
| SPDLOG_INLINE void initialize_logger(std::shared_ptr<logger> logger) | |||
| { | |||
| details::registry::instance().initialize_logger(std::move(logger)); | |||
| } | |||
| SPDLOG_INLINE std::shared_ptr<logger> get(const std::string &name) | |||
| { | |||
| return details::registry::instance().get(name); | |||
| } | |||
| SPDLOG_INLINE void set_formatter(std::unique_ptr<spdlog::formatter> 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<spdlog::formatter>(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 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 flush_every(std::chrono::seconds interval) | |||
| { | |||
| details::registry::instance().flush_every(interval); | |||
| } | |||
| 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> logger) | |||
| { | |||
| details::registry::instance().register_logger(std::move(logger)); | |||
| } | |||
| SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &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_registation) | |||
| { | |||
| details::registry::instance().set_automatic_registration(automatic_registation); | |||
| } | |||
| SPDLOG_INLINE std::shared_ptr<spdlog::logger> 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<spdlog::logger> default_logger) | |||
| { | |||
| details::registry::instance().set_default_logger(std::move(default_logger)); | |||
| } | |||
| } // namespace spdlog | |||
| @@ -0,0 +1,342 @@ | |||
| // 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 "spdlog/common.h" | |||
| #include "spdlog/details/registry.h" | |||
| #include "spdlog/logger.h" | |||
| #include "spdlog/version.h" | |||
| #include "spdlog/details/synchronous_factory.h" | |||
| #include <chrono> | |||
| #include <functional> | |||
| #include <memory> | |||
| #include <string> | |||
| 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 the | |||
| // global settings. | |||
| // | |||
| // Example: | |||
| // spdlog::create<daily_file_sink_st>("logger_name", "dailylog_filename", 11, 59); | |||
| template<typename Sink, typename... SinkArgs> | |||
| inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... sink_args) | |||
| { | |||
| return default_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); | |||
| } | |||
| // Initialize and register a logger, | |||
| // formatter and flush level will be set according the global settings. | |||
| // | |||
| // NOTE: | |||
| // Use this function when creating loggers manually. | |||
| // | |||
| // Example: | |||
| // auto console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>(); | |||
| // auto console_logger = std::make_shared<spdlog::logger>("console_logger", console_sink); | |||
| // spdlog::initialize_logger(console_logger); | |||
| void initialize_logger(std::shared_ptr<logger> logger); | |||
| // Return an existing logger or nullptr if a logger with such name doesn't | |||
| // exist. | |||
| // example: spdlog::get("my_logger")->info("hello {}", "world"); | |||
| std::shared_ptr<logger> get(const std::string &name); | |||
| // Set global formatter. Each sink in each logger will get a clone of this object | |||
| void set_formatter(std::unique_ptr<spdlog::formatter> formatter); | |||
| // Set global format string. | |||
| // example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); | |||
| void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); | |||
| // enable global backtrace support | |||
| void enable_backtrace(size_t n_messages); | |||
| // disable global backtrace support | |||
| void disable_backtrace(); | |||
| // call dump backtrace on default logger | |||
| void dump_backtrace(); | |||
| // Set global logging level | |||
| void set_level(level::level_enum log_level); | |||
| // Set global flush level | |||
| void flush_on(level::level_enum log_level); | |||
| // Start/Restart a periodic flusher thread | |||
| // Warning: Use only if all your loggers are thread safe! | |||
| void flush_every(std::chrono::seconds interval); | |||
| // Set global error handler | |||
| void set_error_handler(void (*handler)(const std::string &msg)); | |||
| // Register the given logger with the given name | |||
| void register_logger(std::shared_ptr<logger> logger); | |||
| // Apply a user defined function on all registered loggers | |||
| // Example: | |||
| // spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); | |||
| void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun); | |||
| // Drop the reference to the given logger | |||
| void drop(const std::string &name); | |||
| // Drop all references from the registry | |||
| void drop_all(); | |||
| // stop any running threads started by spdlog and clean registry loggers | |||
| void shutdown(); | |||
| // Automatic registration of loggers when using spdlog::create() or spdlog::create_async | |||
| void set_automatic_registration(bool automatic_registation); | |||
| // 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 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. | |||
| std::shared_ptr<spdlog::logger> default_logger(); | |||
| spdlog::logger *default_logger_raw(); | |||
| void set_default_logger(std::shared_ptr<spdlog::logger> default_logger); | |||
| template<typename... Args> | |||
| inline void log(source_loc source, level::level_enum lvl, string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->log(source, lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void log(level::level_enum lvl, string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->log(source_loc{}, lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void trace(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->trace(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void debug(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->debug(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void info(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->info(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void warn(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->warn(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void error(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->error(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void critical(string_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->critical(fmt, args...); | |||
| } | |||
| template<typename T> | |||
| inline void log(source_loc source, level::level_enum lvl, const T &msg) | |||
| { | |||
| default_logger_raw()->log(source, lvl, msg); | |||
| } | |||
| template<typename T> | |||
| inline void log(level::level_enum lvl, const T &msg) | |||
| { | |||
| default_logger_raw()->log(lvl, msg); | |||
| } | |||
| template<typename T> | |||
| inline void trace(const T &msg) | |||
| { | |||
| default_logger_raw()->trace(msg); | |||
| } | |||
| template<typename T> | |||
| inline void debug(const T &msg) | |||
| { | |||
| default_logger_raw()->debug(msg); | |||
| } | |||
| template<typename T> | |||
| inline void info(const T &msg) | |||
| { | |||
| default_logger_raw()->info(msg); | |||
| } | |||
| template<typename T> | |||
| inline void warn(const T &msg) | |||
| { | |||
| default_logger_raw()->warn(msg); | |||
| } | |||
| template<typename T> | |||
| inline void error(const T &msg) | |||
| { | |||
| default_logger_raw()->error(msg); | |||
| } | |||
| template<typename T> | |||
| inline void critical(const T &msg) | |||
| { | |||
| default_logger_raw()->critical(msg); | |||
| } | |||
| #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| template<typename... Args> | |||
| inline void log(source_loc source, level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->log(source, lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->log(lvl, fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void trace(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->trace(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void debug(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->debug(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void info(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->info(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void warn(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->warn(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void error(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->error(fmt, args...); | |||
| } | |||
| template<typename... Args> | |||
| inline void critical(wstring_view_t fmt, const Args &... args) | |||
| { | |||
| default_logger_raw()->critical(fmt, args...); | |||
| } | |||
| #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT | |||
| } // 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 | |||
| // | |||
| #define SPDLOG_LOGGER_CALL(logger, level, ...) logger->log(spdlog::source_loc{__FILE__, __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__) | |||
| #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 | |||
| @@ -0,0 +1,136 @@ | |||
| // 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 date/time logging is not needed and never appear in the log | |||
| // pattern. | |||
| // This will prevent spdlog from querying the clock on each log call. | |||
| // | |||
| // WARNING: If the log pattern contains any date/time while this flag is on, the | |||
| // result is undefined. | |||
| // You must set new pattern(spdlog::set_pattern(..") without any | |||
| // date/time in it | |||
| // | |||
| // #define SPDLOG_NO_DATETIME | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| // 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, the result is undefined. | |||
| // | |||
| // #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 if logger name logging is not needed. | |||
| // This will prevent spdlog from copying the logger name on each log call. | |||
| // | |||
| // #define SPDLOG_NO_NAME | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| // 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 use your own copy of the fmt library instead of spdlog's copy. | |||
| // In this case spdlog will try to include <fmt/format.h> so set your -I flag | |||
| // accordingly. | |||
| // | |||
| // #define SPDLOG_FMT_EXTERNAL | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| // 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. "MT TRACE") | |||
| // | |||
| // #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", | |||
| // "MY ERROR", "MY CRITICAL", "OFF" } | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| // 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. | |||
| // | |||
| // #define SPDLOG_FUNCTION __PRETTY_FUNCTION__ | |||
| /////////////////////////////////////////////////////////////////////////////// | |||
| @@ -0,0 +1,10 @@ | |||
| // 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 4 | |||
| #define SPDLOG_VER_PATCH 2 | |||
| #define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) | |||
| @@ -0,0 +1 @@ | |||
| Private-Include: external/spdlog/include | |||
| @@ -0,0 +1,15 @@ | |||
| imports: | |||
| spdlog: external/spdlog | |||
| taywee-args: external/taywee-args | |||
| git module spdlog: | |||
| url: https://github.com/gabime/spdlog.git | |||
| rev: v1.4.2 | |||
| pick: include | |||
| git module taywee-args: | |||
| url: https://github.com/Taywee/args.git | |||
| rev: 6.2.2 | |||
| move: | |||
| args.hxx: include/args.hxx | |||
| pick: include | |||
| @@ -0,0 +1,202 @@ | |||
| #include "./build.hpp" | |||
| #include <dds/logging.hpp> | |||
| #include <dds/proc.hpp> | |||
| #include <dds/toolchain.hpp> | |||
| #include <algorithm> | |||
| #include <iomanip> | |||
| #include <iostream> | |||
| #include <stdexcept> | |||
| using namespace dds; | |||
| namespace { | |||
| struct compile_failure : std::runtime_error { | |||
| using runtime_error::runtime_error; | |||
| }; | |||
| struct archive_failure : std::runtime_error { | |||
| using runtime_error::runtime_error; | |||
| }; | |||
| struct source_files { | |||
| std::vector<fs::path> headers; | |||
| std::vector<fs::path> sources; | |||
| }; | |||
| void collect_sources(source_files& sf, const fs::path& source_dir) { | |||
| static std::vector<std::string_view> header_exts = { | |||
| ".h", | |||
| ".H", | |||
| ".H++", | |||
| ".h++", | |||
| ".hh", | |||
| ".hpp", | |||
| ".hxx", | |||
| ".inl", | |||
| }; | |||
| static std::vector<std::string_view> source_exts = { | |||
| ".C", | |||
| ".c", | |||
| ".c++", | |||
| ".cc", | |||
| ".cpp", | |||
| ".cxx", | |||
| }; | |||
| static auto is_header = [&](const fs::path& p) { | |||
| auto found = std::lower_bound(header_exts.begin(), | |||
| header_exts.end(), | |||
| p.extension(), | |||
| std::less<>()); | |||
| return found != header_exts.end() && *found == p.extension(); | |||
| }; | |||
| static auto is_source = [&](const fs::path& p) { | |||
| auto found = std::lower_bound(source_exts.begin(), | |||
| source_exts.end(), | |||
| p.extension(), | |||
| std::less<>()); | |||
| bool is_cpp = found != source_exts.end() && *found == p.extension(); | |||
| auto leaf = p.string(); | |||
| return is_cpp && !ends_with(leaf, ".main.cpp") && !ends_with(leaf, ".test.cpp"); | |||
| }; | |||
| for (auto entry : fs::recursive_directory_iterator(source_dir)) { | |||
| if (!entry.is_regular_file()) { | |||
| continue; | |||
| } | |||
| auto entry_path = entry.path(); | |||
| if (is_header(entry_path)) { | |||
| sf.headers.push_back(std::move(entry_path)); | |||
| } else if (is_source(entry_path)) { | |||
| sf.sources.push_back(std::move(entry_path)); | |||
| } | |||
| } | |||
| } | |||
| fs::path compile_file(fs::path src_path, | |||
| const build_params& params, | |||
| const toolchain& tc, | |||
| const library_manifest& man) { | |||
| auto obj_dir = params.out_root / "obj"; | |||
| auto obj_relpath = fs::relative(src_path, params.root); | |||
| obj_relpath.replace_filename(obj_relpath.filename().string() + ".o"); | |||
| auto obj_path = obj_dir / obj_relpath; | |||
| fs::create_directories(obj_path.parent_path()); | |||
| spdlog::info("Compile file: {}", src_path.string()); | |||
| compile_file_spec spec{src_path, obj_path}; | |||
| spec.include_dirs.push_back(params.root / "src"); | |||
| spec.include_dirs.push_back(params.root / "include"); | |||
| for (auto& inc : man.private_includes) { | |||
| spec.include_dirs.push_back(inc); | |||
| } | |||
| for (auto& def : man.private_defines) { | |||
| spec.definitions.push_back(def); | |||
| } | |||
| auto cmd = tc.create_compile_command(spec); | |||
| auto compile_res = run_proc(cmd); | |||
| if (!compile_res.okay()) { | |||
| spdlog::error("Compilation failed: {}", spec.source_path.string()); | |||
| std::stringstream strm; | |||
| for (auto& arg : cmd) { | |||
| strm << std::quoted(arg) << ' '; | |||
| } | |||
| spdlog::error("Subcommand FAILED: {}\n{}", strm.str(), compile_res.output); | |||
| throw compile_failure("Compilation failed."); | |||
| } | |||
| return obj_path; | |||
| } | |||
| void copy_headers(const fs::path& source, const fs::path& dest, const source_files& sources) { | |||
| for (auto& header_fpath : sources.headers) { | |||
| auto relpath = fs::relative(header_fpath, source); | |||
| auto dest_fpath = dest / relpath; | |||
| spdlog::info("Export header: {}", relpath); | |||
| fs::create_directories(dest_fpath.parent_path()); | |||
| fs::copy_file(header_fpath, dest_fpath); | |||
| } | |||
| } | |||
| void generate_export(const build_params& params, | |||
| fs::path archive_file, | |||
| const source_files& sources) { | |||
| const auto export_root = params.out_root / (params.export_name + ".export-root"); | |||
| spdlog::info("Generating library export: {}", export_root); | |||
| fs::remove_all(export_root); | |||
| fs::create_directories(export_root); | |||
| fs::copy_file(archive_file, export_root / archive_file.filename()); | |||
| auto header_root = params.root / "include"; | |||
| if (!fs::is_directory(header_root)) { | |||
| header_root = params.root / "src"; | |||
| } | |||
| if (fs::is_directory(header_root)) { | |||
| copy_headers(header_root, export_root / "include", sources); | |||
| } | |||
| } | |||
| } // namespace | |||
| void dds::build(const build_params& params, const library_manifest& man) { | |||
| auto tc = toolchain::load_from_file(params.toolchain_file); | |||
| auto include_dir = params.root / "include"; | |||
| auto src_dir = params.root / "src"; | |||
| source_files files; | |||
| if (fs::exists(include_dir)) { | |||
| if (!fs::is_directory(include_dir)) { | |||
| throw std::runtime_error("The `include` at the root of the project is not a directory"); | |||
| } | |||
| collect_sources(files, include_dir); | |||
| for (auto&& sf : files.sources) { | |||
| spdlog::warn("Source file in `include/` will not be compiled: {}", sf); | |||
| } | |||
| // Drop any source files we found within `include/` | |||
| files.sources.clear(); | |||
| } | |||
| if (fs::exists(src_dir)) { | |||
| if (!fs::is_directory(src_dir)) { | |||
| throw std::runtime_error("The `src` at the root of the project is not a directory"); | |||
| } | |||
| collect_sources(files, src_dir); | |||
| } | |||
| if (files.sources.empty() && files.headers.empty()) { | |||
| spdlog::warn("No source files found to compile/export!"); | |||
| } | |||
| archive_spec arc; | |||
| for (auto&& sf : files.sources) { | |||
| arc.input_files.push_back(compile_file(sf, params, tc, man)); | |||
| } | |||
| arc.out_path = params.out_root / ("lib" + params.export_name + tc.archive_suffix()); | |||
| spdlog::info("Create archive {}", arc.out_path); | |||
| auto ar_cmd = tc.create_archive_command(arc); | |||
| auto ar_res = run_proc(ar_cmd); | |||
| if (!ar_res.okay()) { | |||
| spdlog::error("Failure creating archive library {}", arc.out_path); | |||
| std::stringstream strm; | |||
| for (auto& arg : ar_cmd) { | |||
| strm << std::quoted(arg) << ' '; | |||
| } | |||
| spdlog::error("Subcommand failed: {}", strm.str()); | |||
| spdlog::error("Subcommand produced output:\n{}", ar_res.output); | |||
| throw archive_failure("Failed to create the library archive"); | |||
| } | |||
| if (params.do_export) { | |||
| generate_export(params, arc.out_path, files); | |||
| } | |||
| } | |||
| @@ -0,0 +1,23 @@ | |||
| #ifndef DDS_BUILD_HPP_INCLUDED | |||
| #define DDS_BUILD_HPP_INCLUDED | |||
| #include <dds/util.hpp> | |||
| #include <dds/manifest.hpp> | |||
| #include <optional> | |||
| namespace dds { | |||
| struct build_params { | |||
| fs::path root; | |||
| fs::path out_root; | |||
| fs::path toolchain_file; | |||
| std::string export_name; | |||
| bool do_export = false; | |||
| }; | |||
| void build(const build_params&, const library_manifest& man); | |||
| } // namespace dds | |||
| #endif // DDS_BUILD_HPP_INCLUDED | |||
| @@ -0,0 +1,102 @@ | |||
| #include <dds/build.hpp> | |||
| #include <dds/lm_parse.hpp> | |||
| #include <dds/util.hpp> | |||
| #include <dds/logging.hpp> | |||
| #include <args.hxx> | |||
| #include <filesystem> | |||
| #include <iostream> | |||
| namespace { | |||
| using string_flag = args::ValueFlag<std::string>; | |||
| using path_flag = args::ValueFlag<dds::fs::path>; | |||
| struct cli_base { | |||
| args::ArgumentParser& parser; | |||
| args::HelpFlag _help{parser, "help", "Display this help message and exit", {'h', "help"}}; | |||
| std::shared_ptr<spdlog::logger> console = dds::get_logger(); | |||
| args::Group cmd_group{parser, "Available Commands"}; | |||
| }; | |||
| struct cli_build { | |||
| cli_base& base; | |||
| args::Command cmd{base.cmd_group, "build", "Build a library"}; | |||
| args::HelpFlag _help{cmd, "help", "Display this help message and exit", {'h', "help"}}; | |||
| path_flag lib_dir{cmd, | |||
| "lib_dir", | |||
| "The path to the directory containing the library", | |||
| {"lib-dir"}, | |||
| dds::fs::current_path()}; | |||
| path_flag out_dir{cmd, | |||
| "out_dir", | |||
| "The directory in which to write the built files", | |||
| {"out-dir"}, | |||
| dds::fs::current_path() / "_build"}; | |||
| string_flag export_name{cmd, | |||
| "export_name", | |||
| "Set the name of the export", | |||
| {"export-name", 'n'}, | |||
| dds::fs::current_path().filename()}; | |||
| path_flag tc_filepath{cmd, | |||
| "toolchain_file", | |||
| "Path to the toolchain file to use", | |||
| {"toolchain-file", 'T'}, | |||
| dds::fs::current_path() / "toolchain.dds"}; | |||
| args::Flag export_{cmd, "export_dir", "Generate a library export", {"export", 'E'}}; | |||
| int run() { | |||
| dds::build_params params; | |||
| params.root = lib_dir.Get(); | |||
| params.out_root = out_dir.Get(); | |||
| params.toolchain_file = tc_filepath.Get(); | |||
| params.export_name = export_name.Get(); | |||
| params.do_export = export_.Get(); | |||
| dds::library_manifest man; | |||
| const auto man_filepath = params.root / "manifest.dds"; | |||
| if (exists(man_filepath)) { | |||
| man = dds::library_manifest::load_from_file(man_filepath); | |||
| } | |||
| dds::build(params, man); | |||
| return 0; | |||
| } | |||
| }; | |||
| } // namespace | |||
| int main(int argc, char** argv) { | |||
| args::ArgumentParser parser("DDSLiM - The drop-dead-simple library manager"); | |||
| cli_base cli{parser}; | |||
| cli_build build{cli}; | |||
| try { | |||
| parser.ParseCLI(argc, argv); | |||
| } catch (const args::Help&) { | |||
| std::cout << parser; | |||
| return 0; | |||
| } catch (const args::Error& e) { | |||
| std::cerr << parser; | |||
| std::cerr << e.what() << '\n'; | |||
| return 1; | |||
| } | |||
| try { | |||
| if (build.cmd) { | |||
| return build.run(); | |||
| } else { | |||
| assert(false); | |||
| std::terminate(); | |||
| } | |||
| } catch (const std::exception& e) { | |||
| spdlog::critical(e.what()); | |||
| return 2; | |||
| } | |||
| } | |||
| @@ -0,0 +1,96 @@ | |||
| #include "./lm_parse.hpp" | |||
| #include <dds/util.hpp> | |||
| #include <fstream> | |||
| namespace fs = std::filesystem; | |||
| using namespace std::literals; | |||
| using namespace dds; | |||
| namespace { | |||
| std::string_view sview(std::string_view::const_iterator beg, std::string_view::const_iterator end) { | |||
| return std::string_view{beg, static_cast<std::size_t>(std::distance(beg, end))}; | |||
| } | |||
| std::string_view trim(std::string_view s) { | |||
| auto iter = s.begin(); | |||
| auto end = s.end(); | |||
| while (iter != end && std::isspace(*iter)) { | |||
| ++iter; | |||
| } | |||
| auto riter = s.rbegin(); | |||
| auto rend = s.rend(); | |||
| while (riter != rend && std::isspace(*riter)) { | |||
| ++riter; | |||
| } | |||
| auto new_end = riter.base(); | |||
| return sview(iter, new_end); | |||
| } | |||
| void parse_line(std::vector<lm_pair>& pairs, const std::string_view whole_line) { | |||
| const auto line = trim(whole_line); | |||
| if (line.empty() || line[0] == '#') { | |||
| return; | |||
| } | |||
| const auto begin = line.begin(); | |||
| auto iter = begin; | |||
| const auto end = line.end(); | |||
| while (true) { | |||
| if (iter == end) { | |||
| throw std::runtime_error("Invalid line in config file: '"s + std::string(whole_line) | |||
| + "'"); | |||
| } | |||
| if (*iter == ':') { | |||
| if (++iter == end) { | |||
| // Empty value | |||
| break; | |||
| } else if (*iter == ' ') { | |||
| // Found the key | |||
| break; | |||
| } else { | |||
| // Just a regular character. Keep going... | |||
| } | |||
| } | |||
| ++iter; | |||
| } | |||
| // `iter` now points to the space between the key and value | |||
| auto key = sview(begin, iter - 1); // -1 to trim the colon in the key | |||
| auto value = sview(iter, end); | |||
| key = trim(key); | |||
| value = trim(value); | |||
| pairs.emplace_back(key, value); | |||
| } | |||
| } // namespace | |||
| dds::lm_kv_pairs dds::lm_parse_string(std::string_view s) { | |||
| std::vector<lm_pair> pairs; | |||
| auto line_begin = s.begin(); | |||
| auto iter = line_begin; | |||
| auto end = s.end(); | |||
| while (iter != end) { | |||
| if (*iter == '\n') { | |||
| parse_line(pairs, sview(line_begin, iter)); | |||
| line_begin = ++iter; | |||
| continue; | |||
| } | |||
| ++iter; | |||
| } | |||
| if (line_begin != end) { | |||
| parse_line(pairs, sview(line_begin, end)); | |||
| } | |||
| return lm_kv_pairs(std::move(pairs)); | |||
| } | |||
| dds::lm_kv_pairs dds::lm_parse_file(fs::path fpath) { | |||
| return lm_parse_string(slurp_file(fpath)); | |||
| } | |||
| @@ -0,0 +1,116 @@ | |||
| #ifndef DDS_LM_PARSE_HPP_INCLUDED | |||
| #define DDS_LM_PARSE_HPP_INCLUDED | |||
| #include <cassert> | |||
| #include <filesystem> | |||
| #include <string> | |||
| #include <utility> | |||
| #include <vector> | |||
| namespace dds { | |||
| class lm_pair { | |||
| std::string _key; | |||
| std::string _value; | |||
| public: | |||
| lm_pair(std::string_view k, std::string_view v) | |||
| : _key(k) | |||
| , _value(v) {} | |||
| auto& key() const noexcept { return _key; } | |||
| auto& value() const noexcept { return _value; } | |||
| auto tie() const noexcept { return std::tie(key(), value()); } | |||
| }; | |||
| struct kv_pair_iterator { | |||
| using vec_type = std::vector<lm_pair>; | |||
| using base_iter = vec_type::const_iterator; | |||
| base_iter _iter; | |||
| base_iter _end; | |||
| std::string _key; | |||
| public: | |||
| using iterator_category = std::forward_iterator_tag; | |||
| using difference_type = vec_type::difference_type; | |||
| using value_type = vec_type::value_type; | |||
| using pointer = vec_type::pointer; | |||
| using reference = vec_type::reference; | |||
| inline kv_pair_iterator(base_iter, base_iter, std::string_view k); | |||
| kv_pair_iterator& operator++() & noexcept { | |||
| assert(_iter != _end); | |||
| ++_iter; | |||
| while (_iter != _end && _iter->key() != _key) { | |||
| ++_iter; | |||
| } | |||
| return *this; | |||
| } | |||
| const lm_pair* operator->() const noexcept { | |||
| assert(_iter != _end); | |||
| return _iter.operator->(); | |||
| } | |||
| const lm_pair& operator*() const noexcept { | |||
| assert(_iter != _end); | |||
| return *_iter; | |||
| } | |||
| inline bool operator!=(const kv_pair_iterator& o) const noexcept { return _iter != o._iter; } | |||
| auto begin() const noexcept { return *this; } | |||
| auto end() const noexcept { return kv_pair_iterator(_end, _end, _key); } | |||
| explicit operator bool() const noexcept { return *this != end(); } | |||
| }; | |||
| class lm_kv_pairs { | |||
| std::vector<lm_pair> _kvs; | |||
| public: | |||
| explicit lm_kv_pairs(std::vector<lm_pair> kvs) | |||
| : _kvs(kvs) {} | |||
| auto& items() const noexcept { return _kvs; } | |||
| const lm_pair* find(const std::string_view& key) const noexcept { | |||
| for (auto&& item : items()) { | |||
| if (item.key() == key) { | |||
| return &item; | |||
| } | |||
| } | |||
| return nullptr; | |||
| } | |||
| kv_pair_iterator iter(std::string_view key) const noexcept { | |||
| auto iter = items().begin(); | |||
| const auto end = items().end(); | |||
| while (iter != end && iter->key() != key) { | |||
| ++iter; | |||
| } | |||
| return kv_pair_iterator{iter, end, key}; | |||
| } | |||
| std::vector<lm_pair> all_of(std::string_view key) const noexcept { | |||
| auto iter = this->iter(key); | |||
| return std::vector<lm_pair>(iter, iter.end()); | |||
| } | |||
| auto size() const noexcept { return _kvs.size(); } | |||
| }; | |||
| inline kv_pair_iterator::kv_pair_iterator(base_iter it, base_iter end, std::string_view k) | |||
| : _iter{it} | |||
| , _end{end} | |||
| , _key{k} {} | |||
| lm_kv_pairs lm_parse_string(std::string_view); | |||
| lm_kv_pairs lm_parse_file(std::filesystem::path); | |||
| void lm_write_pairs(std::filesystem::path, lm_kv_pairs); | |||
| } // namespace dds | |||
| #endif // DDS_LM_PARSE_HPP_INCLUDED | |||
| @@ -0,0 +1,84 @@ | |||
| #include <dds/lm_parse.hpp> | |||
| #include <dds/util.test.hpp> | |||
| #include <iostream> | |||
| using namespace dds; | |||
| void test_simple() { | |||
| auto lm_src = ""; | |||
| auto kvs = lm_parse_string(lm_src); | |||
| CHECK(kvs.size() == 0); | |||
| lm_src = "foo: bar"; | |||
| kvs = lm_parse_string(lm_src); | |||
| CHECK(kvs.size() == 1); | |||
| REQUIRE(kvs.find("foo")); | |||
| CHECK(kvs.find("foo")->value() == "bar"); | |||
| lm_src = "foo:bar: baz"; | |||
| kvs = lm_parse_string(lm_src); | |||
| CHECK(kvs.size() == 1); | |||
| REQUIRE(kvs.find("foo:bar")); | |||
| CHECK(kvs.find("foo:bar")->value() == "baz"); | |||
| CHECK(lm_parse_string("#comment").size() == 0); | |||
| CHECK(lm_parse_string("\n\n").size() == 0); | |||
| CHECK(lm_parse_string("\n#comment").size() == 0); | |||
| CHECK(lm_parse_string("#comment\n\n").size() == 0); | |||
| std::vector<std::string_view> empty_foos = { | |||
| "Foo:", | |||
| "Foo: ", | |||
| "Foo:\n", | |||
| "Foo: \n", | |||
| "\n\nFoo:", | |||
| " Foo:", | |||
| " Foo: ", | |||
| "Foo :", | |||
| "Foo :\n", | |||
| }; | |||
| for (auto s : empty_foos) { | |||
| kvs = lm_parse_string(s); | |||
| CHECK(kvs.size() == 1); | |||
| REQUIRE(kvs.find("Foo")); | |||
| CHECK(kvs.find("Foo")->value() == ""); | |||
| } | |||
| kvs = lm_parse_string("foo: # Not a comment"); | |||
| CHECK(kvs.size() == 1); | |||
| REQUIRE(kvs.find("foo")); | |||
| CHECK(kvs.find("foo")->value() == "# Not a comment"); | |||
| } | |||
| void test_multi() { | |||
| auto kvs = lm_parse_string("Foo: bar\nbaz: qux"); | |||
| CHECK(kvs.size() == 2); | |||
| REQUIRE(kvs.find("Foo")); | |||
| CHECK(kvs.find("Foo")->value() == "bar"); | |||
| REQUIRE(kvs.find("baz")); | |||
| CHECK(kvs.find("baz")->value() == "qux"); | |||
| kvs = lm_parse_string("foo: first\nfoo: second\n"); | |||
| CHECK(kvs.size() == 2); | |||
| auto iter = kvs.iter("foo"); | |||
| REQUIRE(iter); | |||
| CHECK(iter->key() == "foo"); | |||
| CHECK(iter->value() == "first"); | |||
| ++iter; | |||
| REQUIRE(iter); | |||
| CHECK(iter->key() == "foo"); | |||
| CHECK(iter->value() == "second"); | |||
| ++iter; | |||
| CHECK(!iter); | |||
| iter = kvs.iter("no-exist"); | |||
| CHECK(!iter); | |||
| } | |||
| void run_tests() { | |||
| test_simple(); | |||
| test_multi(); | |||
| } | |||
| DDS_TEST_MAIN; | |||
| @@ -0,0 +1,14 @@ | |||
| #ifndef DDS_LOGGING_HPP_INCLUDED | |||
| #define DDS_LOGGING_HPP_INCLUDED | |||
| #include <spdlog/fmt/ostr.h> | |||
| #include <spdlog/sinks/stdout_color_sinks.h> | |||
| #include <spdlog/spdlog.h> | |||
| namespace dds { | |||
| inline auto get_logger() { return spdlog::stdout_color_mt("console"); } | |||
| } // namespace dds | |||
| #endif // DDS_LOGGING_HPP_INCLUDED | |||
| @@ -0,0 +1,20 @@ | |||
| #include "./manifest.hpp" | |||
| #include <dds/lm_parse.hpp> | |||
| using namespace dds; | |||
| library_manifest library_manifest::load_from_file(const fs::path& fpath) { | |||
| auto kvs = lm_parse_file(fpath); | |||
| library_manifest ret; | |||
| for (auto& pair : kvs.items()) { | |||
| if (pair.key() == "Private-Include") { | |||
| ret.private_includes.emplace_back(pair.value()); | |||
| } else if (pair.key() == "Private-Defines") { | |||
| ret.private_defines.emplace_back(pair.value()); | |||
| } else { | |||
| throw std::runtime_error("Unknown key in " + fpath.string() + ": " + pair.key()); | |||
| } | |||
| } | |||
| return ret; | |||
| } | |||
| @@ -0,0 +1,17 @@ | |||
| #ifndef DDS_MANIFEST_HPP_INCLUDED | |||
| #define DDS_MANIFEST_HPP_INCLUDED | |||
| #include <dds/util.hpp> | |||
| namespace dds { | |||
| struct library_manifest { | |||
| std::vector<fs::path> private_includes; | |||
| std::vector<std::string> private_defines; | |||
| static library_manifest load_from_file(const fs::path&); | |||
| }; | |||
| } // namespace dds | |||
| #endif // DDS_MANIFEST_HPP_INCLUDED | |||
| @@ -0,0 +1,93 @@ | |||
| #include "./proc.hpp" | |||
| #include <poll.h> | |||
| #include <sys/wait.h> | |||
| #include <unistd.h> | |||
| #include <cassert> | |||
| #include <cerrno> | |||
| #include <deque> | |||
| #include <iostream> | |||
| #include <system_error> | |||
| using namespace dds; | |||
| namespace { | |||
| void check_rc(bool b, std::string_view s) { | |||
| if (!b) { | |||
| throw std::system_error(std::error_code(errno, std::system_category()), std::string(s)); | |||
| } | |||
| } | |||
| ::pid_t | |||
| spawn_child(const std::vector<std::string>& command, int stdout_pipe, int close_me) noexcept { | |||
| auto child_pid = ::fork(); | |||
| if (child_pid != 0) { | |||
| return child_pid; | |||
| } | |||
| // We are child | |||
| ::close(close_me); | |||
| auto rc = dup2(stdout_pipe, STDOUT_FILENO); | |||
| check_rc(rc != -1, "Failed to dup2 stdout"); | |||
| rc = dup2(stdout_pipe, STDERR_FILENO); | |||
| check_rc(rc != -1, "Failed to dup2 stderr"); | |||
| std::vector<const char*> strings; | |||
| strings.reserve(command.size() + 1); | |||
| for (auto& s : command) { | |||
| strings.push_back(s.data()); | |||
| } | |||
| strings.push_back(nullptr); | |||
| ::execvp(strings[0], (char* const*)strings.data()); | |||
| std::cerr << "[ddslim child executor] execvp returned! This is a fatal error: " | |||
| << std::system_category().message(errno) << '\n'; | |||
| std::terminate(); | |||
| } | |||
| } // namespace | |||
| proc_result dds::run_proc(const std::vector<std::string>& command) { | |||
| int stdio_pipe[2] = {}; | |||
| auto rc = ::pipe(stdio_pipe); | |||
| check_rc(rc == 0, "Create stdio pipe for subprocess"); | |||
| int read_pipe = stdio_pipe[0]; | |||
| int write_pipe = stdio_pipe[1]; | |||
| auto child = spawn_child(command, write_pipe, read_pipe); | |||
| ::close(write_pipe); | |||
| pollfd stdio_fd; | |||
| stdio_fd.fd = read_pipe; | |||
| stdio_fd.events = POLLIN; | |||
| proc_result res; | |||
| while (true) { | |||
| rc = ::poll(&stdio_fd, 1, -1); | |||
| check_rc(rc > 0, "Failed in poll()"); | |||
| std::string buffer; | |||
| buffer.resize(1024); | |||
| auto nread = ::read(stdio_fd.fd, buffer.data(), buffer.size()); | |||
| if (nread == 0) { | |||
| break; | |||
| } | |||
| check_rc(nread > 0, "Failed in read()"); | |||
| res.output.append(buffer.begin(), buffer.begin() + nread); | |||
| } | |||
| int status = 0; | |||
| rc = ::waitpid(child, &status, 0); | |||
| check_rc(rc >= 0, "Failed in waitpid()"); | |||
| if (WIFEXITED(status)) { | |||
| res.retc = WEXITSTATUS(status); | |||
| } else if (WIFSIGNALED(status)) { | |||
| res.signal = WTERMSIG(status); | |||
| } | |||
| return res; | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| #ifndef DDS_PROC_HPP_INCLUDED | |||
| #define DDS_PROC_HPP_INCLUDED | |||
| #include <string> | |||
| #include <vector> | |||
| namespace dds { | |||
| struct proc_result { | |||
| int signal = 0; | |||
| int retc = 0; | |||
| std::string output; | |||
| bool okay() const noexcept { return retc == 0 && signal == 0; } | |||
| }; | |||
| proc_result run_proc(const std::vector<std::string>& args); | |||
| } // namespace dds | |||
| #endif // DDS_PROC_HPP_INCLUDED | |||
| @@ -0,0 +1,217 @@ | |||
| #include "./toolchain.hpp" | |||
| #include <dds/lm_parse.hpp> | |||
| #include <optional> | |||
| #include <string> | |||
| #include <vector> | |||
| using namespace dds; | |||
| using std::optional; | |||
| using std::string; | |||
| using std::string_view; | |||
| using std::vector; | |||
| using opt_string = optional<string>; | |||
| namespace { | |||
| struct invalid_toolchain : std::runtime_error { | |||
| using std::runtime_error::runtime_error; | |||
| }; | |||
| } // namespace | |||
| toolchain toolchain::load_from_file(fs::path p) { | |||
| opt_string inc_template; | |||
| opt_string def_template; | |||
| opt_string c_compile_template; | |||
| opt_string cxx_compile_template; | |||
| opt_string create_archive_template; | |||
| opt_string archive_suffix; | |||
| auto require_key = [](auto k, auto& opt) { | |||
| if (!opt.has_value()) { | |||
| throw invalid_toolchain("Toolchain file is missing a required key: " + string(k)); | |||
| } | |||
| }; | |||
| auto kvs = lm_parse_file(p); | |||
| for (auto&& pair : kvs.items()) { | |||
| auto& key = pair.key(); | |||
| auto& value = pair.value(); | |||
| auto try_single = [&](auto k, auto& opt) { | |||
| if (key == k) { | |||
| if (opt.has_value()) { | |||
| throw invalid_toolchain("Duplicate key: " + key); | |||
| } | |||
| opt = value; | |||
| return true; | |||
| } | |||
| return false; | |||
| }; | |||
| // clang-format off | |||
| bool found_single = false // Bool to force alignment | |||
| // Argument templates | |||
| || try_single("Include-Template", inc_template) | |||
| || try_single("Define-Template", def_template) | |||
| // Command templates | |||
| || try_single("Compile-C-Template", c_compile_template) | |||
| || try_single("Compile-C++-Template", cxx_compile_template) | |||
| || try_single("Create-Archive-Template", create_archive_template) | |||
| || try_single("Archive-Suffix", archive_suffix) | |||
| || false; | |||
| // clang-format on | |||
| if (!found_single) { | |||
| throw invalid_toolchain("Unknown toolchain file key: " + key); | |||
| } | |||
| } | |||
| require_key("Include-Template", inc_template); | |||
| require_key("Define-Template", def_template); | |||
| require_key("Compile-C-Template", c_compile_template); | |||
| require_key("Compile-C++-Template", cxx_compile_template); | |||
| require_key("Create-Archive-Template", create_archive_template); | |||
| require_key("Archive-Suffix", archive_suffix); | |||
| return toolchain{ | |||
| c_compile_template.value(), | |||
| cxx_compile_template.value(), | |||
| inc_template.value(), | |||
| def_template.value(), | |||
| create_archive_template.value(), | |||
| archive_suffix.value(), | |||
| }; | |||
| } | |||
| vector<string> dds::split_shell_string(std::string_view shell) { | |||
| char cur_quote = 0; | |||
| bool is_escaped = false; | |||
| vector<string> acc; | |||
| const auto begin = shell.begin(); | |||
| auto iter = begin; | |||
| const auto end = shell.end(); | |||
| opt_string token; | |||
| while (iter != end) { | |||
| const char c = *iter++; | |||
| if (is_escaped) { | |||
| if (c == '\n') { | |||
| // Ignore the newline | |||
| } else if (cur_quote || c != cur_quote || c == '\\') { | |||
| // Escaped `\` character | |||
| token = token.value_or("") + c; | |||
| } else { | |||
| // Regular escape sequence | |||
| token = token.value_or("") + '\\' + c; | |||
| } | |||
| is_escaped = false; | |||
| } else if (c == '\\') { | |||
| is_escaped = true; | |||
| } else if (cur_quote) { | |||
| if (c == cur_quote) { | |||
| // End of quoted token; | |||
| cur_quote = 0; | |||
| } else { | |||
| token = token.value_or("") + c; | |||
| } | |||
| } else if (c == '"' || c == '\'') { | |||
| // Beginning of a quoted token | |||
| cur_quote = c; | |||
| token = ""; | |||
| } else if (c == '\t' || c == ' ' || c == '\n' || c == '\r' || c == '\f') { | |||
| // We've reached unquoted whitespace | |||
| if (token.has_value()) { | |||
| acc.push_back(move(*token)); | |||
| } | |||
| token.reset(); | |||
| } else { | |||
| // Just a regular character | |||
| token = token.value_or("") + c; | |||
| } | |||
| } | |||
| if (token.has_value()) { | |||
| acc.push_back(move(*token)); | |||
| } | |||
| return acc; | |||
| } | |||
| namespace { | |||
| std::string replace(std::string_view str, std::string_view key, std::string_view repl) { | |||
| std::string ret; | |||
| std::string_view::size_type pos = 0; | |||
| std::string_view::size_type prev_pos = 0; | |||
| while (pos = str.find(key, pos), pos != key.npos) { | |||
| ret.append(str.begin() + prev_pos, str.begin() + pos); | |||
| ret.append(repl); | |||
| prev_pos = pos += key.size(); | |||
| } | |||
| ret.append(str.begin() + prev_pos, str.end()); | |||
| return ret; | |||
| } | |||
| vector<string> replace(vector<string> strings, std::string_view key, std::string_view repl) { | |||
| for (auto& item : strings) { | |||
| item = replace(item, key, repl); | |||
| } | |||
| return strings; | |||
| } | |||
| } // namespace | |||
| vector<string> toolchain::include_args(const fs::path& p) const noexcept { | |||
| return replace(_inc_template, "<PATH>", p.string()); | |||
| } | |||
| vector<string> toolchain::definition_args(std::string_view s) const noexcept { | |||
| return replace(_def_template, "<DEF>", s); | |||
| } | |||
| vector<string> toolchain::create_compile_command(const compile_file_spec& spec) const noexcept { | |||
| vector<string> flags; | |||
| for (auto&& inc_dir : spec.include_dirs) { | |||
| auto inc_args = include_args(inc_dir); | |||
| flags.insert(flags.end(), inc_args.begin(), inc_args.end()); | |||
| } | |||
| for (auto&& def : spec.definitions) { | |||
| auto def_args = definition_args(def); | |||
| flags.insert(flags.end(), def_args.begin(), def_args.end()); | |||
| } | |||
| vector<string> command; | |||
| for (auto arg : _cxx_compile) { | |||
| if (arg == "<FLAGS>") { | |||
| command.insert(command.end(), flags.begin(), flags.end()); | |||
| } else { | |||
| arg = replace(arg, "<FILE>", spec.source_path.string()); | |||
| arg = replace(arg, "<OUT>", spec.out_path.string()); | |||
| command.push_back(arg); | |||
| } | |||
| } | |||
| return command; | |||
| } | |||
| vector<string> toolchain::create_archive_command(const archive_spec& spec) const noexcept { | |||
| vector<string> cmd; | |||
| for (auto& arg : _archive_template) { | |||
| if (arg == "<OBJECTS>") { | |||
| cmd.insert(cmd.end(), spec.input_files.begin(), spec.input_files.end()); | |||
| } else { | |||
| cmd.push_back(replace(arg, "<ARCHIVE>", spec.out_path.string())); | |||
| } | |||
| } | |||
| return cmd; | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| #ifndef DDS_TOOLCHAIN_HPP_INCLUDED | |||
| #define DDS_TOOLCHAIN_HPP_INCLUDED | |||
| #include <dds/util.hpp> | |||
| #include <string> | |||
| #include <vector> | |||
| namespace dds { | |||
| std::vector<std::string> split_shell_string(std::string_view s); | |||
| enum class language { | |||
| automatic, | |||
| c, | |||
| cxx, | |||
| }; | |||
| struct compile_file_spec { | |||
| fs::path source_path; | |||
| fs::path out_path; | |||
| std::vector<std::string> definitions = {}; | |||
| std::vector<fs::path> include_dirs = {}; | |||
| language lang = language::automatic; | |||
| }; | |||
| struct archive_spec { | |||
| std::vector<fs::path> input_files; | |||
| fs::path out_path; | |||
| }; | |||
| class toolchain { | |||
| using string_seq = std::vector<std::string>; | |||
| string_seq _c_compile; | |||
| string_seq _cxx_compile; | |||
| string_seq _inc_template; | |||
| string_seq _def_template; | |||
| string_seq _archive_template; | |||
| std::string _archive_suffix; | |||
| public: | |||
| toolchain(const std::string& c_compile, | |||
| const std::string& cxx_compile, | |||
| const std::string& inc_template, | |||
| const std::string& def_template, | |||
| const std::string& archive_template, | |||
| const std::string& archive_suffix) | |||
| : _c_compile(split_shell_string(c_compile)) | |||
| , _cxx_compile(split_shell_string(cxx_compile)) | |||
| , _inc_template(split_shell_string(inc_template)) | |||
| , _def_template(split_shell_string(def_template)) | |||
| , _archive_template(split_shell_string(archive_template)) | |||
| , _archive_suffix(archive_suffix) {} | |||
| static toolchain load_from_file(fs::path); | |||
| auto& archive_suffix() const noexcept { return _archive_suffix; } | |||
| std::vector<std::string> definition_args(std::string_view s) const noexcept; | |||
| std::vector<std::string> include_args(const fs::path& p) const noexcept; | |||
| std::vector<std::string> create_compile_command(const compile_file_spec&) const noexcept; | |||
| std::vector<std::string> create_archive_command(const archive_spec&) const noexcept; | |||
| }; | |||
| } // namespace dds | |||
| #endif // DDS_TOOLCHAIN_HPP_INCLUDED | |||
| @@ -0,0 +1,35 @@ | |||
| #include <dds/toolchain.hpp> | |||
| #include <dds/util.test.hpp> | |||
| using namespace dds; | |||
| namespace { | |||
| #define CHECK_SHLEX(str, ...) \ | |||
| do { \ | |||
| CHECK(dds::split_shell_string(str) == std::vector<std::string>(__VA_ARGS__)); \ | |||
| } while (0) | |||
| void test_shlex() { | |||
| CHECK_SHLEX("foo", {"foo"}); | |||
| CHECK_SHLEX("foo bar", {"foo", "bar"}); | |||
| CHECK_SHLEX("\"foo\" bar", {"foo", "bar"}); | |||
| CHECK_SHLEX("\"foo bar\"", {"foo bar"}); | |||
| CHECK_SHLEX("", {}); | |||
| CHECK_SHLEX(" ", {}); | |||
| CHECK_SHLEX("\"\"", {""}); | |||
| CHECK_SHLEX("'quoted arg'", {"quoted arg"}); | |||
| CHECK_SHLEX("word ", {"word"}); | |||
| CHECK_SHLEX("\" meow\"", {" meow"}); | |||
| CHECK_SHLEX("foo bar", {"foo", "bar"}); | |||
| CHECK_SHLEX("C:\\\\Program\\ Files", {"C:\\Program Files"}); | |||
| CHECK_SHLEX("Foo\nBar", {"Foo", "Bar"}); | |||
| CHECK_SHLEX("foo \"\" bar", {"foo", "", "bar"}); | |||
| } | |||
| void run_tests() { test_shlex(); } | |||
| } // namespace | |||
| DDS_TEST_MAIN; | |||
| @@ -0,0 +1,29 @@ | |||
| #include "./util.hpp" | |||
| #include <filesystem> | |||
| #include <fstream> | |||
| std::fstream dds::open(const fs::path& filepath, std::ios::openmode mode, std::error_code& ec) { | |||
| std::fstream ret; | |||
| auto mask = ret.exceptions() | std::ios::failbit; | |||
| ret.exceptions(mask); | |||
| try { | |||
| ret.open(filepath.string(), mode); | |||
| } catch (const std::ios::failure& e) { | |||
| ec = std::error_code(errno, std::system_category()); | |||
| } | |||
| return ret; | |||
| } | |||
| std::string dds::slurp_file(const fs::path& path, std::error_code& ec) { | |||
| auto file = dds::open(path, std::ios::in, ec); | |||
| if (ec) { | |||
| return std::string{}; | |||
| } | |||
| std::ostringstream out; | |||
| out << file.rdbuf(); | |||
| return std::move(out).str(); | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| #ifndef DDS_UTIL_HPP_INCLUDED | |||
| #define DDS_UTIL_HPP_INCLUDED | |||
| #include <filesystem> | |||
| #include <fstream> | |||
| namespace dds { | |||
| namespace fs = std::filesystem; | |||
| std::fstream open(const fs::path& filepath, std::ios::openmode mode, std::error_code& ec); | |||
| std::string slurp_file(const fs::path& path, std::error_code& ec); | |||
| inline std::fstream open(const fs::path& filepath, std::ios::openmode mode) { | |||
| std::error_code ec; | |||
| auto ret = dds::open(filepath, mode, ec); | |||
| if (ec) { | |||
| throw std::system_error{ec, "Error opening file: " + filepath.string()}; | |||
| } | |||
| return ret; | |||
| } | |||
| inline std::string slurp_file(const fs::path& path) { | |||
| std::error_code ec; | |||
| auto contents = dds::slurp_file(path, ec); | |||
| if (ec) { | |||
| throw std::system_error{ec, "Reading file: " + path.string()}; | |||
| } | |||
| return contents; | |||
| } | |||
| inline bool ends_with(std::string_view s, std::string_view key) { | |||
| auto found = s.find(key); | |||
| return found != s.npos && found == s.size() - key.size(); | |||
| } | |||
| } // namespace dds | |||
| #endif // DDS_UTIL_HPP_INCLUDED | |||
| @@ -0,0 +1,47 @@ | |||
| #ifndef DDS_UTIL_TEST_HPP_INCLUDED | |||
| #define DDS_UTIL_TEST_HPP_INCLUDED | |||
| #include <iostream> | |||
| namespace dds { | |||
| int S_failed_checks = 0; | |||
| struct requirement_failed {}; | |||
| #define CHECK(...) \ | |||
| do { \ | |||
| if (!(__VA_ARGS__)) { \ | |||
| ++::dds::S_failed_checks; \ | |||
| std::cerr << "Check failed at " << __FILE__ << ':' << __LINE__ << ": " << #__VA_ARGS__ \ | |||
| << "\n"; \ | |||
| } \ | |||
| } while (0) | |||
| #define REQUIRE(...) \ | |||
| do { \ | |||
| if (!(__VA_ARGS__)) { \ | |||
| ++::dds::S_failed_checks; \ | |||
| std::cerr << "Check failed at " << __FILE__ << ':' << __LINE__ << ": " << #__VA_ARGS__ \ | |||
| << "\n"; \ | |||
| throw requirement_failed(); \ | |||
| } \ | |||
| } while (0) | |||
| #define DDS_TEST_MAIN \ | |||
| int main() { \ | |||
| try { \ | |||
| run_tests(); \ | |||
| } catch (const requirement_failed&) { \ | |||
| return S_failed_checks; \ | |||
| } catch (const std::exception& e) { \ | |||
| std::cerr << "An unhandled exception occured: " << e.what() << '\n'; \ | |||
| return 2; \ | |||
| } \ | |||
| return ::dds::S_failed_checks; \ | |||
| } \ | |||
| static_assert(true) | |||
| } // namespace dds | |||
| #endif // DDS_UTIL_TEST_HPP_INCLUDED | |||