mirror of
https://github.com/mutouyun/cpp-ipc.git
synced 2025-12-06 16:56:45 +08:00
Add fmt
This commit is contained in:
parent
e717ad46fb
commit
7e7bfb8467
176
include/libipc/imp/fmt.h
Normal file
176
include/libipc/imp/fmt.h
Normal file
@ -0,0 +1,176 @@
|
||||
/**
|
||||
* \file libipc/fmt.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief String formatting.
|
||||
*
|
||||
* \remarks The current performance is not high,
|
||||
* because I use std::sprintf directly for formatting for convenience.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <chrono> // std::chrono::time_point
|
||||
#include <tuple>
|
||||
#include <cstddef>
|
||||
#include <ctime> // std::tm, std::localtime
|
||||
|
||||
#include "libipc/imp/fmt_cpo.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief The format string reference wrapper.
|
||||
*/
|
||||
template <typename T>
|
||||
struct fmt_ref {
|
||||
span<char const> fstr;
|
||||
T param;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Conversion specifiers.
|
||||
*
|
||||
* \remarks Just like printf, the format string is of the form
|
||||
* [flags][field_width][.precision][conversion_character]
|
||||
*
|
||||
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
|
||||
*/
|
||||
template <std::size_t N>
|
||||
auto spec(char const (&fstr)[N]) noexcept {
|
||||
return [&fstr](auto &&arg) noexcept {
|
||||
using arg_t = decltype(arg);
|
||||
return fmt_ref<arg_t> {{fstr}, static_cast<arg_t>(arg)};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief String formatting function.
|
||||
*
|
||||
* \param args arguments that support the fmt output
|
||||
* \return an empty string if the fmt output fails
|
||||
*/
|
||||
template <typename... A>
|
||||
LIBIPC_NODISCARD std::string fmt(A &&...args) {
|
||||
std::string joined;
|
||||
fmt_context ctx(joined);
|
||||
if (fmt_to(ctx, std::forward<A>(args)...)) {
|
||||
return ctx.finish() ? joined : "";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/// \brief String types.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, char const * a) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, std::string const &a) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, char const * a, span<char const> fstr) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, std::string const &a, span<char const> fstr) noexcept { return to_string(ctx, a.c_str(), fstr); }
|
||||
|
||||
/// \brief Character to string conversion.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, char a) noexcept;
|
||||
#if defined(LIBIPC_CPP_20)
|
||||
inline bool to_string(fmt_context &ctx, char8_t a) noexcept { return to_string(ctx, (char)a); }
|
||||
#endif // defined(LIBIPC_CPP_20)
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, wchar_t a) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, char16_t a) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, char32_t a) noexcept;
|
||||
|
||||
/// \brief Conversion of numeric types to strings.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, signed short a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, signed int a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, signed long a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr = {}) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, signed char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (int)a, fstr); }
|
||||
inline bool to_string(fmt_context &ctx, unsigned char a, span<char const> fstr = {}) noexcept { return to_string(ctx, (unsigned)a, fstr); }
|
||||
|
||||
/// \brief Conversion of floating point type to strings.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, double a, span<char const> fstr = {}) noexcept;
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, long double a, span<char const> fstr = {}) noexcept;
|
||||
inline bool to_string(fmt_context &ctx, float a, span<char const> fstr = {}) noexcept { return to_string(ctx, (double)a, fstr); }
|
||||
|
||||
/// \brief Pointer.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, std::nullptr_t) noexcept;
|
||||
template <typename T,
|
||||
typename = std::enable_if_t<std::is_same<T, void>::value>>
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, T const volatile *a) noexcept;
|
||||
|
||||
/// \brief Date and time.
|
||||
IPC_EXPORT bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr = {}) noexcept;
|
||||
|
||||
namespace detail_fmt {
|
||||
|
||||
/**
|
||||
* \brief Convert std::time_t to std::string.
|
||||
* \return an empty string if the conversion fails
|
||||
*/
|
||||
inline bool time_to_string(fmt_context &ctx, std::time_t tt, span<char const> fstr) noexcept {
|
||||
#if defined(LIBIPC_CC_MSVC)
|
||||
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-s-localtime32-s-localtime64-s
|
||||
std::tm tm {};
|
||||
if (::localtime_s(&tm, &tt) != 0) {
|
||||
return {};
|
||||
}
|
||||
return to_string(ctx, tm, fstr);
|
||||
#else
|
||||
return to_string(ctx, *std::localtime(&tt), fstr);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail_fmt
|
||||
|
||||
template <class Clock, class Duration>
|
||||
bool to_string(fmt_context &ctx, std::chrono::time_point<Clock, Duration> const &a, span<char const> fstr = {}) noexcept {
|
||||
return detail_fmt::time_to_string(ctx, std::chrono::system_clock::to_time_t(a), fstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Predefined fmt_to method
|
||||
*/
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
template <typename T>
|
||||
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, T &&arg) noexcept
|
||||
-> decltype(ipc::to_string(ctx, std::forward<T>(arg))) {
|
||||
return ipc::to_string(ctx, std::forward<T>(arg));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, fmt_ref<T> arg) noexcept
|
||||
-> decltype(ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr)) {
|
||||
return ipc::to_string(ctx, static_cast<T>(arg.param), arg.fstr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, span<T> s) {
|
||||
if (s.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (!fmt_to(ctx, s[0])) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 1; i < s.size(); ++i) {
|
||||
if (!fmt_to(ctx, ' ', s[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Tp, std::size_t... I>
|
||||
bool unfold_tuple_fmt_to(fmt_context &ctx, Tp const &tp, std::index_sequence<I...>) {
|
||||
return fmt_to(ctx, std::get<I>(tp)...);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
bool tag_invoke(decltype(ipc::fmt_to), fmt_context &ctx, std::tuple<T...> const &tp) {
|
||||
return unfold_tuple_fmt_to(ctx, tp, std::index_sequence_for<T...>{});
|
||||
}
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
} // namespace ipc
|
||||
66
include/libipc/imp/fmt_cpo.h
Normal file
66
include/libipc/imp/fmt_cpo.h
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* \file libipc/fmt_cpo.h
|
||||
* \author mutouyun (orz@orzz.org)
|
||||
* \brief String formatting CPO.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <cstddef>
|
||||
#include <array>
|
||||
|
||||
#include "libipc/imp/generic.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
#include "libipc/imp/span.h"
|
||||
#include "libipc/imp/export.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \class class IPC_EXPORT fmt_context
|
||||
* \brief The context of fmt.
|
||||
*/
|
||||
class IPC_EXPORT fmt_context {
|
||||
std::array<char, 2048U> sbuf_; ///< stack buffer
|
||||
|
||||
std::string &joined_;
|
||||
std::size_t offset_;
|
||||
|
||||
public:
|
||||
fmt_context(std::string &j) noexcept;
|
||||
|
||||
std::size_t capacity() noexcept;
|
||||
void reset() noexcept;
|
||||
bool finish() noexcept;
|
||||
span<char> buffer(std::size_t sz) noexcept;
|
||||
void expend(std::size_t sz) noexcept;
|
||||
bool append(span<char const> const &str) noexcept;
|
||||
};
|
||||
|
||||
/// \brief Supports custom fmt_to methods for ipc::fmt.
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
class fmt_to_t {
|
||||
template <typename A1>
|
||||
bool get_result(fmt_context &ctx, A1 && a1) const {
|
||||
return ipc::tag_invoke(fmt_to_t{}, ctx, std::forward<A1>(a1));
|
||||
}
|
||||
|
||||
template <typename A1, typename... A>
|
||||
bool get_result(fmt_context &ctx, A1 && a1, A &&...args) const {
|
||||
return get_result(ctx, std::forward<A1>(a1))
|
||||
&& get_result(ctx, std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename... A>
|
||||
bool operator()(fmt_context &ctx, A &&...args) const {
|
||||
return get_result(ctx, std::forward<A>(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail_tag_invoke
|
||||
|
||||
constexpr detail_tag_invoke::fmt_to_t fmt_to{};
|
||||
|
||||
} // namespace ipc
|
||||
@ -44,7 +44,7 @@ constexpr in_place_t in_place {};
|
||||
* \brief A general pattern for supporting customisable functions
|
||||
* \see https://www.open-std.org/jtc1/sc22/WG21/docs/papers/2019/p1895r0.pdf
|
||||
*/
|
||||
namespace detail {
|
||||
namespace detail_tag_invoke {
|
||||
|
||||
void tag_invoke();
|
||||
|
||||
@ -57,15 +57,15 @@ struct tag_invoke_t {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace detail_tag_invoke
|
||||
|
||||
constexpr detail::tag_invoke_t tag_invoke {};
|
||||
constexpr detail_tag_invoke::tag_invoke_t tag_invoke{};
|
||||
|
||||
/**
|
||||
* \brief Circumventing forwarding reference may override copy and move constructs.
|
||||
* \see https://mpark.github.io/programming/2014/06/07/beware-of-perfect-forwarding-constructors/
|
||||
*/
|
||||
namespace detail {
|
||||
namespace detail_not_match {
|
||||
|
||||
template <typename T, typename... A>
|
||||
struct is_same_first : std::false_type {};
|
||||
@ -73,11 +73,11 @@ struct is_same_first : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_same_first<T, T> : std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
} // namespace detail_not_match
|
||||
|
||||
template <typename T, typename... A>
|
||||
using not_match =
|
||||
typename std::enable_if<!detail::is_same_first<T,
|
||||
typename std::enable_if<!detail_not_match::is_same_first<T,
|
||||
typename std::decay<A>::type...>::value, bool>::type;
|
||||
|
||||
/**
|
||||
|
||||
326
src/libipc/imp/fmt.cpp
Normal file
326
src/libipc/imp/fmt.cpp
Normal file
@ -0,0 +1,326 @@
|
||||
|
||||
#include <cstdio> // std::snprintf
|
||||
#include <iomanip> // std::put_time
|
||||
#include <sstream> // std::ostringstream
|
||||
#include <array>
|
||||
#include <cstring> // std::memcpy
|
||||
#include <algorithm> // std::min
|
||||
#include <initializer_list>
|
||||
#include <cstdint>
|
||||
|
||||
#include "libipc/imp/fmt.h"
|
||||
#include "libipc/imp/codecvt.h"
|
||||
#include "libipc/imp/detect_plat.h"
|
||||
|
||||
namespace ipc {
|
||||
|
||||
/**
|
||||
* \brief Format conversions helpers.
|
||||
* \see http://personal.ee.surrey.ac.uk/Personal/R.Bowden/C/printf.html
|
||||
* https://en.cppreference.com/w/cpp/io/c/fprintf
|
||||
*/
|
||||
namespace {
|
||||
|
||||
struct sfmt_policy {
|
||||
static constexpr std::size_t aligned_size = 32U;
|
||||
};
|
||||
|
||||
template <typename Policy = sfmt_policy>
|
||||
span<char> local_fmt_sbuf() noexcept {
|
||||
thread_local std::array<char, Policy::aligned_size> sbuf;
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
span<char const> normalize(span<char const> const &a) {
|
||||
if (a.empty()) return {};
|
||||
return a.first(a.size() - (a.back() == '\0' ? 1 : 0));
|
||||
}
|
||||
|
||||
span<char> smem_cpy(span<char> const &sbuf, span<char const> a) noexcept {
|
||||
if (sbuf.empty()) return {};
|
||||
a = normalize(a);
|
||||
auto sz = (std::min)(sbuf.size() - 1, a.size());
|
||||
if (sz != 0) std::memcpy(sbuf.data(), a.data(), sz);
|
||||
return sbuf.first(sz);
|
||||
}
|
||||
|
||||
span<char> sbuf_cpy(span<char> sbuf, span<char const> const &a) noexcept {
|
||||
sbuf = smem_cpy(sbuf, a);
|
||||
*sbuf.end() = '\0';
|
||||
return sbuf;
|
||||
}
|
||||
|
||||
span<char> sbuf_cat(span<char> const &sbuf, std::initializer_list<span<char const>> args) noexcept {
|
||||
std::size_t remain = sbuf.size();
|
||||
for (auto s : args) {
|
||||
remain -= smem_cpy(sbuf.last(remain), s).size();
|
||||
}
|
||||
auto sz = sbuf.size() - remain;
|
||||
sbuf[sz] = '\0';
|
||||
return sbuf.first(sz);
|
||||
}
|
||||
|
||||
char const *as_cstr(span<char const> const &a) {
|
||||
if (a.empty()) return "";
|
||||
if (a.back() == '\0') return a.data();
|
||||
return sbuf_cpy(local_fmt_sbuf(), a).data();
|
||||
}
|
||||
|
||||
span<char> fmt_of(span<char const> const &fstr, span<char const> const &s) {
|
||||
return sbuf_cat(local_fmt_sbuf(), {"%", fstr, s});
|
||||
}
|
||||
|
||||
span<char> fmt_of_unsigned(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "u");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'o':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'u': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "u"});
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_of_signed(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "d");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'o':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'u': return fmt_of_unsigned(fstr, l);
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "d"});
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_of_float(span<char const> fstr, span<char const> const &l) {
|
||||
if (fstr.empty()) {
|
||||
return fmt_of(l, "f");
|
||||
}
|
||||
fstr = normalize(fstr);
|
||||
switch (fstr.back()) {
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'g':
|
||||
case 'G': return sbuf_cat(local_fmt_sbuf(), {"%", fstr.first(fstr.size() - 1), l, fstr.last(1)});
|
||||
default : return sbuf_cat(local_fmt_sbuf(), {"%", fstr, l, "f"});
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A /*a fundamental or pointer type*/>
|
||||
int sprintf(fmt_context &ctx, span<char const> const &sfmt, A a) {
|
||||
for (std::int32_t sz = -1;;) {
|
||||
auto sbuf = ctx.buffer(sz + 1);
|
||||
if (sbuf.size() < std::size_t(sz + 1)) {
|
||||
return -1;
|
||||
}
|
||||
sz = std::snprintf(sbuf.data(), sbuf.size(), sfmt.data(), a);
|
||||
if (sz <= 0) {
|
||||
return sz;
|
||||
}
|
||||
if (std::size_t(sz) < sbuf.size()) {
|
||||
ctx.expend(sz);
|
||||
return sz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename F /*a function pointer*/,
|
||||
typename A /*a fundamental or pointer type*/>
|
||||
bool sprintf(fmt_context &ctx, F fop, span<char const> const &fstr, span<char const> const &s, A a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
return ipc::sprintf(ctx, fop(fstr, s), a) >= 0;
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// \brief The context of fmt.
|
||||
|
||||
fmt_context::fmt_context(std::string &j) noexcept
|
||||
: joined_(j)
|
||||
, offset_(0) {}
|
||||
|
||||
std::size_t fmt_context::capacity() noexcept {
|
||||
return (offset_ < sbuf_.size()) ? sbuf_.size() : joined_.size();
|
||||
}
|
||||
|
||||
void fmt_context::reset() noexcept {
|
||||
offset_ = 0;
|
||||
}
|
||||
|
||||
bool fmt_context::finish() noexcept {
|
||||
LIBIPC_TRY {
|
||||
if (offset_ < sbuf_.size()) {
|
||||
joined_.assign(sbuf_.data(), offset_);
|
||||
} else {
|
||||
joined_.resize(offset_);
|
||||
}
|
||||
return true;
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
span<char> fmt_context::buffer(std::size_t sz) noexcept {
|
||||
auto roundup = [](std::size_t sz) noexcept {
|
||||
constexpr std::size_t fmt_context_aligned_size = 512U;
|
||||
return (sz & ~(fmt_context_aligned_size - 1)) + fmt_context_aligned_size;
|
||||
};
|
||||
auto sbuf = make_span(sbuf_);
|
||||
LIBIPC_TRY {
|
||||
if (offset_ < sbuf.size()) {
|
||||
if ((offset_ + sz) < sbuf.size()) {
|
||||
return sbuf.subspan(offset_);
|
||||
} else {
|
||||
/// \remark switch the cache to std::string
|
||||
joined_.assign(sbuf.data(), offset_);
|
||||
joined_.resize(roundup(offset_ + sz));
|
||||
}
|
||||
} else if ((offset_ + sz) >= joined_.size()) {
|
||||
joined_.resize(roundup(offset_ + sz));
|
||||
}
|
||||
return {&joined_[offset_], joined_.size() - offset_};
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void fmt_context::expend(std::size_t sz) noexcept {
|
||||
offset_ += sz;
|
||||
}
|
||||
|
||||
bool fmt_context::append(span<char const> const &str) noexcept {
|
||||
auto sz = str.size();
|
||||
if (str.back() == '\0') --sz;
|
||||
auto sbuf = buffer(sz);
|
||||
if (sbuf.size() < sz) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(sbuf.data(), str.data(), sz);
|
||||
offset_ += sz;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \brief To string conversion.
|
||||
|
||||
bool to_string(fmt_context &ctx, char const *a) noexcept {
|
||||
return to_string(ctx, a, {});
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::string const &a) noexcept {
|
||||
return ctx.append(a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char const *a, span<char const> fstr) noexcept {
|
||||
if (a == nullptr) return false;
|
||||
return ipc::sprintf(ctx, fmt_of, fstr, "s", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char a) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of, {}, "c", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, wchar_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::wstring{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char16_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::u16string{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, char32_t a) noexcept {
|
||||
LIBIPC_TRY {
|
||||
std::string des;
|
||||
cvt_sstr(std::u32string{a}, des);
|
||||
return ctx.append(des);
|
||||
} LIBIPC_CATCH(...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed short a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "h", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned short a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "h", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed int a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned int a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "l", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "l", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, signed long long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_signed, fstr, "ll", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, unsigned long long a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_unsigned, fstr, "ll", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, double a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_float, fstr, "", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, long double a, span<char const> fstr) noexcept {
|
||||
return ipc::sprintf(ctx, fmt_of_float, fstr, "L", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::nullptr_t) noexcept {
|
||||
return ctx.append("null");
|
||||
}
|
||||
|
||||
template <>
|
||||
bool to_string<void, void>(fmt_context &ctx, void const volatile *a) noexcept {
|
||||
if (a == nullptr) {
|
||||
return to_string(ctx, nullptr);
|
||||
}
|
||||
return ipc::sprintf(ctx, fmt_of, "", "p", a);
|
||||
}
|
||||
|
||||
bool to_string(fmt_context &ctx, std::tm const &a, span<char const> fstr) noexcept {
|
||||
if (fstr.empty()) {
|
||||
fstr = "%Y-%m-%d %H:%M:%S";
|
||||
}
|
||||
LIBIPC_TRY {
|
||||
std::ostringstream ss;
|
||||
ss << std::put_time(&a, as_cstr(fstr));
|
||||
return ctx.append(ss.str());
|
||||
} LIBIPC_CATCH(...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ipc
|
||||
125
test/imp/test_imp_fmt.cpp
Normal file
125
test/imp/test_imp_fmt.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <cstring>
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#include "libipc/imp/fmt.h"
|
||||
|
||||
TEST(fmt, spec) {
|
||||
EXPECT_STREQ(ipc::spec("hello")(123).fstr.data(), "hello");
|
||||
EXPECT_EQ(ipc::spec("hello")(123).param , 123);
|
||||
EXPECT_STREQ(ipc::spec("hello")("world").fstr.data(), "hello");
|
||||
EXPECT_STREQ(ipc::spec("hello")("world").param , "world");
|
||||
}
|
||||
|
||||
TEST(fmt, to_string) {
|
||||
std::string joined;
|
||||
ipc::fmt_context ctx(joined);
|
||||
|
||||
auto check = [&](auto &&txt, auto &&...val) {
|
||||
ctx.reset();
|
||||
EXPECT_TRUE(ipc::to_string(ctx, std::forward<decltype(val)>(val)...));
|
||||
ctx.finish();
|
||||
EXPECT_EQ(joined, std::forward<decltype(txt)>(txt));
|
||||
};
|
||||
|
||||
/// \brief string
|
||||
check("", "");
|
||||
check("%what%", "%what%");
|
||||
check(" %what%", "%what%", "10");
|
||||
check("%what% ", "%what%", "-10");
|
||||
|
||||
/// \brief character
|
||||
check("A", 'A');
|
||||
check("A", L'A');
|
||||
check("A", u'A');
|
||||
check("A", U'A');
|
||||
|
||||
/// \brief numeric
|
||||
check("123" , (signed char)123 );
|
||||
check("-65" , (signed char)-321 );
|
||||
check("123" , (unsigned char)123 );
|
||||
check("65" , (unsigned char)321 );
|
||||
check("123" , (short)123 );
|
||||
check("-321" , (short)-321 );
|
||||
check("123" , (unsigned short)123 );
|
||||
check("321" , (unsigned short)321 );
|
||||
check("-7949" , (short)123123 );
|
||||
check("6359" , (short)-321321 );
|
||||
check("57587" , (unsigned short)123123);
|
||||
check("59177" , (unsigned short)321321);
|
||||
check("123123" , 123123 );
|
||||
check("-321321" , -321321 );
|
||||
check("123123" , 123123u );
|
||||
check("321321" , 321321u );
|
||||
check("123123" , 123123ll );
|
||||
check("-321321" , -321321ll );
|
||||
check("123123" , 123123ull );
|
||||
check("321321" , 321321ull );
|
||||
check("1e0f3" , 123123, "x" );
|
||||
check("1e0f3" , 123123u, "x" );
|
||||
check("1CAAB5C3B3", 123123123123ll, "X" );
|
||||
check("1CAAB5C3B3", 123123123123ull, "X" );
|
||||
|
||||
/// \brief floating point
|
||||
check("123.123" , 123.123f, ".3");
|
||||
check("0123.12300" , 123.123, "010.5");
|
||||
check("123.123000" , 123.123l, "010.6");
|
||||
check("1.500000e+00", 1.5, "e");
|
||||
check("1.500000E+00", 1.5, "E");
|
||||
double r = 0.0;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 0.0/r)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, 1.0/r)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
|
||||
/// \brief pointer
|
||||
check("null", nullptr);
|
||||
int *p = (int *)0x0f013a04;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, (void *)p)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
|
||||
/// \brief date and time
|
||||
auto tp = std::chrono::system_clock::now();
|
||||
auto tt = std::chrono::system_clock::to_time_t(tp);
|
||||
auto tm = *std::localtime(&tt);
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tm)); ctx.finish();
|
||||
std::cout << joined << "\n";
|
||||
std::string tm_str = joined;
|
||||
ctx.reset(); EXPECT_TRUE(ipc::to_string(ctx, tp)); ctx.finish();
|
||||
EXPECT_EQ(tm_str, joined);
|
||||
}
|
||||
|
||||
TEST(fmt, fmt) {
|
||||
char const txt[] = "hello world.";
|
||||
|
||||
/// \brief hello world
|
||||
auto s = ipc::fmt("hello", " ", "world", ".");
|
||||
EXPECT_EQ(s, txt);
|
||||
|
||||
/// \brief chrono
|
||||
std::cout << ipc::fmt('[', std::chrono::system_clock::now(), "] ", s) << "\n";
|
||||
|
||||
/// \brief long string
|
||||
s = ipc::fmt(ipc::spec("4096")(txt));
|
||||
std::string test(4096, ' ');
|
||||
std::memcpy(&test[test.size() - sizeof(txt) + 1], txt, sizeof(txt) - 1);
|
||||
EXPECT_EQ(s, test);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class foo {};
|
||||
|
||||
bool tag_invoke(decltype(ipc::fmt_to), ipc::fmt_context &, foo arg) noexcept(false) {
|
||||
throw arg;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(fmt, throw) {
|
||||
EXPECT_THROW(std::ignore = ipc::fmt(foo{}), foo);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user