add: [imp] fmt

This commit is contained in:
mutouyun 2022-11-27 21:04:25 +08:00
parent 44fe8ce8da
commit bbbb03408d
3 changed files with 417 additions and 0 deletions

95
include/libimp/fmt.h Normal file
View File

@ -0,0 +1,95 @@
/**
* @file libimp/fmt.h
* @author mutouyun (orz@orzz.org)
* @brief String formatting.
* @date 2022-11-26
*/
#pragma once
#include <string>
#include <utility>
#include <type_traits>
#include <chrono> // std::chrono::time_point
#include <cstddef>
#include <ctime> // std::tm
#include "libimp/def.h"
#include "libimp/span.h"
#include "libimp/detect_plat.h"
#include "libimp/export.h"
LIBIMP_NAMESPACE_BEG_
template <typename T>
struct fmt_ref {
span<char const> fstr;
T param;
};
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)};
};
}
template <typename... A>
std::string fmt(A &&...args) {
std::string joined;
LIBIMP_UNUSED auto unfold = {
joined.append(to_string(std::forward<A>(args)))...
};
return joined;
}
/// @brief Return the string directly.
LIBIMP_EXPORT std::string const &to_string(std::string const &a) noexcept;
LIBIMP_EXPORT std::string to_string(std::string const &a, span<char const> fstr) noexcept;
/// @brief Character to string conversion.
/// @return an empty string if the conversion fails
LIBIMP_EXPORT std::string to_string(char a) noexcept;
LIBIMP_EXPORT std::string to_string(wchar_t a) noexcept;
LIBIMP_EXPORT std::string to_string(char16_t a) noexcept;
LIBIMP_EXPORT std::string to_string(char32_t a) noexcept;
#if defined(LIBIMP_CPP_20)
LIBIMP_EXPORT std::string to_string(char8_t a) noexcept;
#endif // defined(LIBIMP_CPP_20)
/// @brief Conversion of numeric types to strings.
/// @return an empty string if the conversion fails
LIBIMP_EXPORT std::string to_string(signed char a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(unsigned char a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(signed short a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(unsigned short a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(signed int a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(unsigned int a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(signed long a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(unsigned long a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(signed long long a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(unsigned long long a, span<char const> fstr = {}) noexcept;
/// @brief Conversion of floating point type to strings.
/// @return an empty string if the conversion fails
LIBIMP_EXPORT std::string to_string(float a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(double a, span<char const> fstr = {}) noexcept;
LIBIMP_EXPORT std::string to_string(long double a, span<char const> fstr = {}) noexcept;
/// @brief Pointer.
LIBIMP_EXPORT std::string to_string(std::nullptr_t) noexcept;
template <typename T,
typename = std::enable_if_t<std::is_same<T, void>::value>>
LIBIMP_EXPORT std::string to_string(T *a) noexcept;
/// @brief Date and time.
LIBIMP_EXPORT std::string to_string(std::tm const &a, span<char const> fstr = {}) noexcept;
namespace detail {
LIBIMP_EXPORT std::string time_to_string(std::time_t tt, span<char const> fstr) noexcept;
} // namespace detail
template <class Clock, class Duration>
LIBIMP_EXPORT std::string to_string(std::chrono::time_point<Clock, Duration> const &a, span<char const> fstr = {}) noexcept {
return detail::time_to_string(std::chrono::system_clock::to_time_t(a), fstr);
}
LIBIMP_NAMESPACE_END_

235
src/libimp/fmt.cpp Normal file
View File

@ -0,0 +1,235 @@
#define _CRT_SECURE_NO_WARNINGS
#include "libimp/fmt.h"
#include <cstdio> // std::snprintf
#include <iomanip> // std::put_time
#include <sstream> // std::ostringstream
#include "libimp/codecvt.h"
LIBIMP_NAMESPACE_BEG_
/**
* @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 {
span<char const> normalize(span<char const> a) {
if (a.empty()) return {};
return a.first(a.size() - (a.back() == '\0' ? 1 : 0));
}
std::string as_string(span<char const> a) {
if (a.empty()) return {};
a = normalize(a);
return std::string(a.data(), a.size());
}
std::string fmt_of(span<char const> fstr, span<char const> s) {
return '%' + as_string(fstr) + as_string(s);
}
std::string fmt_of_unsigned(span<char const> fstr, span<char 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 fmt_of(fstr.first(fstr.size() - 1), l) + fstr.back();
default : return fmt_of(fstr, l) + 'u';
}
}
std::string fmt_of_signed(span<char const> fstr, span<char 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 fmt_of(fstr, l) + 'd';
}
}
std::string fmt_of_float(span<char const> fstr, span<char 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 fmt_of(fstr.first(fstr.size() - 1), l) + fstr.back();
default : return fmt_of(fstr, l) + 'f';
}
}
template <typename A /*a fundamental or pointer type*/>
std::string sprintf(std::string const &sfmt, A a) {
auto sz = std::snprintf(nullptr, 0, sfmt.c_str(), a);
if (sz <= 0) return {};
std::string des;
des.resize(sz + 1);
if (std::snprintf(&des[0], des.size(), sfmt.c_str(), a) < 0) {
return {};
}
des.pop_back(); // remove the terminated null character
return des;
}
template <typename F /*a function pointer*/,
typename A /*a fundamental or pointer type*/>
std::string sprintf(F fop, span<char const> fstr, span<char const> s, A a) noexcept {
LIBIMP_TRY {
return ::LIBIMP::sprintf(fop(fstr, s), a);
} LIBIMP_CATCH(...) {
return {};
}
}
} // namespace
std::string const &to_string(std::string const &a) noexcept {
return a;
}
std::string to_string(std::string const &a, span<char const> fstr) noexcept {
if (a.empty()) return {};
return ::LIBIMP::sprintf(fmt_of, fstr, "s", a.c_str());
}
std::string to_string(char a) noexcept {
return {a};
}
std::string to_string(wchar_t a) noexcept {
LIBIMP_TRY {
std::string des;
cvt_sstr(std::wstring{a}, des);
return des;
} LIBIMP_CATCH(...) {
return {};
}
}
std::string to_string(char16_t a) noexcept {
LIBIMP_TRY {
std::string des;
cvt_sstr(std::u16string{a}, des);
return des;
} LIBIMP_CATCH(...) {
return {};
}
}
std::string to_string(char32_t a) noexcept {
LIBIMP_TRY {
std::string des;
cvt_sstr(std::u32string{a}, des);
return des;
} LIBIMP_CATCH(...) {
return {};
}
}
#if defined(LIBIMP_CPP_20)
std::string to_string(char8_t a) noexcept {
return to_string((char)a);
}
#endif // defined(LIBIMP_CPP_20)
std::string to_string(signed char a, span<char const> fstr) noexcept {
return to_string((int)a, fstr);
}
std::string to_string(unsigned char a, span<char const> fstr) noexcept {
return to_string((unsigned)a, fstr);
}
std::string to_string(signed short a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_signed, fstr, "h", a);
}
std::string to_string(unsigned short a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "h", a);
}
std::string to_string(signed int a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_signed, fstr, "", a);
}
std::string to_string(unsigned int a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "", a);
}
std::string to_string(signed long a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_signed, fstr, "l", a);
}
std::string to_string(unsigned long a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "l", a);
}
std::string to_string(signed long long a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_signed, fstr, "ll", a);
}
std::string to_string(unsigned long long a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "ll", a);
}
std::string to_string(float a, span<char const> fstr) noexcept {
return to_string((double)a, fstr);
}
std::string to_string(double a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_float, fstr, "", a);
}
std::string to_string(long double a, span<char const> fstr) noexcept {
return ::LIBIMP::sprintf(fmt_of_float, fstr, "L", a);
}
std::string to_string(std::nullptr_t) noexcept {
return "null";
}
template <>
std::string to_string<void, void>(void *a) noexcept {
if (a == nullptr) {
return to_string(nullptr);
}
return ::LIBIMP::sprintf(fmt_of, "", "p", a);
}
std::string to_string(std::tm const &a, span<char const> fstr) noexcept {
if (fstr.empty()) {
fstr = "%Y-%m-%d %H:%M:%S";
}
LIBIMP_TRY {
std::ostringstream ss;
ss << std::put_time(&a, as_string(fstr).c_str());
return ss.str();
} LIBIMP_CATCH(...) {
return {};
}
}
namespace detail {
std::string time_to_string(std::time_t tt, span<char const> fstr) noexcept {
return to_string(*std::localtime(&tt), fstr);
}
} // namespace detail
LIBIMP_NAMESPACE_END_

87
test/imp/test_imp_fmt.cpp Normal file
View File

@ -0,0 +1,87 @@
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "gtest/gtest.h"
#include "libimp/fmt.h"
TEST(fmt, operator) {
auto a = imp::spec("hello")(123);
EXPECT_STREQ(a.fstr.data(), "hello");
EXPECT_EQ(a.param , 123);
auto b = imp::spec("hello")("world");
EXPECT_STREQ(b.fstr.data(), "hello");
EXPECT_STREQ(b.param , "world");
}
TEST(fmt, to_string) {
/// @brief string
EXPECT_EQ(imp::to_string(""), "");
EXPECT_EQ(imp::to_string("%what%"), "%what%");
EXPECT_EQ(imp::to_string("%what%", "10") , " %what%");
EXPECT_EQ(imp::to_string("%what%", "-10"), "%what% ");
/// @brief character
EXPECT_EQ(imp::to_string('A'), "A");
EXPECT_EQ(imp::to_string(L'A'), "A");
EXPECT_EQ(imp::to_string(u'A'), "A");
EXPECT_EQ(imp::to_string(U'A'), "A");
/// @brief numeric
EXPECT_EQ(imp::to_string((signed char)123), "123");
EXPECT_EQ(imp::to_string((signed char)-321), "-65");
EXPECT_EQ(imp::to_string((unsigned char)123), "123");
EXPECT_EQ(imp::to_string((unsigned char)321), "65");
EXPECT_EQ(imp::to_string((short)123), "123");
EXPECT_EQ(imp::to_string((short)-321), "-321");
EXPECT_EQ(imp::to_string((unsigned short)123), "123");
EXPECT_EQ(imp::to_string((unsigned short)321), "321");
EXPECT_EQ(imp::to_string((short)123123), "-7949");
EXPECT_EQ(imp::to_string((short)-321321), "6359");
EXPECT_EQ(imp::to_string((unsigned short)123123), "57587");
EXPECT_EQ(imp::to_string((unsigned short)321321), "59177");
EXPECT_EQ(imp::to_string(123123), "123123");
EXPECT_EQ(imp::to_string(-321321), "-321321");
EXPECT_EQ(imp::to_string(123123u), "123123");
EXPECT_EQ(imp::to_string(321321u), "321321");
EXPECT_EQ(imp::to_string(123123ll), "123123");
EXPECT_EQ(imp::to_string(-321321ll), "-321321");
EXPECT_EQ(imp::to_string(123123ull), "123123");
EXPECT_EQ(imp::to_string(321321ull), "321321");
EXPECT_EQ(imp::to_string(123123, "x"), "1e0f3");
EXPECT_EQ(imp::to_string(123123u, "x"), "1e0f3");
EXPECT_EQ(imp::to_string(123123123123ll, "X"), "1CAAB5C3B3");
EXPECT_EQ(imp::to_string(123123123123ull, "X"), "1CAAB5C3B3");
/// @brief floating point
EXPECT_EQ(imp::to_string(123.123f, ".3"), "123.123");
EXPECT_EQ(imp::to_string(123.123, "010.5"), "0123.12300");
EXPECT_EQ(imp::to_string(123.123l, "010.6"), "123.123000");
EXPECT_EQ(imp::to_string(1.5, "e"), "1.500000e+00");
EXPECT_EQ(imp::to_string(1.5, "E"), "1.500000E+00");
double r = 0.0;
std::cout << imp::to_string(0.0/r) << "\n";
std::cout << imp::to_string(1.0/r) << "\n";
SUCCEED();
/// @brief pointer
EXPECT_EQ(imp::to_string(nullptr), "null");
int *p = (int *)0x0f013a04;
std::cout << imp::to_string((void *)p) << "\n";
SUCCEED();
/// @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);
std::cout << imp::to_string(tm) << "\n";
EXPECT_EQ(imp::to_string(tm), imp::to_string(tp));
SUCCEED();
}
TEST(fmt, fmt) {
auto s = imp::fmt("hello", " ", "world", ".");
EXPECT_EQ(s, "hello world.");
std::cout << imp::fmt('[', std::chrono::system_clock::now(), "] ", s) << "\n";
}