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