diff --git a/include/libimp/fmt.h b/include/libimp/fmt.h new file mode 100644 index 0000000..9906982 --- /dev/null +++ b/include/libimp/fmt.h @@ -0,0 +1,95 @@ +/** + * @file libimp/fmt.h + * @author mutouyun (orz@orzz.org) + * @brief String formatting. + * @date 2022-11-26 + */ +#pragma once + +#include +#include +#include +#include // std::chrono::time_point +#include +#include // std::tm + +#include "libimp/def.h" +#include "libimp/span.h" +#include "libimp/detect_plat.h" +#include "libimp/export.h" + +LIBIMP_NAMESPACE_BEG_ + +template +struct fmt_ref { + span fstr; + T param; +}; + +template +auto spec(char const (&fstr)[N]) noexcept { + return [&fstr](auto &&arg) noexcept { + using arg_t = decltype(arg); + return fmt_ref {{fstr}, static_cast(arg)}; + }; +} + +template +std::string fmt(A &&...args) { + std::string joined; + LIBIMP_UNUSED auto unfold = { + joined.append(to_string(std::forward(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 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 fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(unsigned char a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(signed short a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(unsigned short a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(signed int a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(unsigned int a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(signed long a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(unsigned long a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(signed long long a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(unsigned long long a, span 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 fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(double a, span fstr = {}) noexcept; +LIBIMP_EXPORT std::string to_string(long double a, span fstr = {}) noexcept; + +/// @brief Pointer. +LIBIMP_EXPORT std::string to_string(std::nullptr_t) noexcept; +template ::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 fstr = {}) noexcept; +namespace detail { +LIBIMP_EXPORT std::string time_to_string(std::time_t tt, span fstr) noexcept; +} // namespace detail +template +LIBIMP_EXPORT std::string to_string(std::chrono::time_point const &a, span fstr = {}) noexcept { + return detail::time_to_string(std::chrono::system_clock::to_time_t(a), fstr); +} + +LIBIMP_NAMESPACE_END_ diff --git a/src/libimp/fmt.cpp b/src/libimp/fmt.cpp new file mode 100644 index 0000000..f9da2e1 --- /dev/null +++ b/src/libimp/fmt.cpp @@ -0,0 +1,235 @@ +#define _CRT_SECURE_NO_WARNINGS +#include "libimp/fmt.h" + +#include // std::snprintf +#include // std::put_time +#include // 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 normalize(span a) { + if (a.empty()) return {}; + return a.first(a.size() - (a.back() == '\0' ? 1 : 0)); +} + +std::string as_string(span a) { + if (a.empty()) return {}; + a = normalize(a); + return std::string(a.data(), a.size()); +} + +std::string fmt_of(span fstr, span s) { + return '%' + as_string(fstr) + as_string(s); +} + +std::string fmt_of_unsigned(span fstr, span 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 fstr, span 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 fstr, span 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 +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 +std::string sprintf(F fop, span fstr, span 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 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 fstr) noexcept { + return to_string((int)a, fstr); +} + +std::string to_string(unsigned char a, span fstr) noexcept { + return to_string((unsigned)a, fstr); +} + +std::string to_string(signed short a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_signed, fstr, "h", a); +} + +std::string to_string(unsigned short a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "h", a); +} + +std::string to_string(signed int a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_signed, fstr, "", a); +} + +std::string to_string(unsigned int a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "", a); +} + +std::string to_string(signed long a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_signed, fstr, "l", a); +} + +std::string to_string(unsigned long a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "l", a); +} + +std::string to_string(signed long long a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_signed, fstr, "ll", a); +} + +std::string to_string(unsigned long long a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_unsigned, fstr, "ll", a); +} + +std::string to_string(float a, span fstr) noexcept { + return to_string((double)a, fstr); +} + +std::string to_string(double a, span fstr) noexcept { + return ::LIBIMP::sprintf(fmt_of_float, fstr, "", a); +} + +std::string to_string(long double a, span 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 *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 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 fstr) noexcept { + return to_string(*std::localtime(&tt), fstr); +} + +} // namespace detail + +LIBIMP_NAMESPACE_END_ diff --git a/test/imp/test_imp_fmt.cpp b/test/imp/test_imp_fmt.cpp new file mode 100644 index 0000000..5ecb906 --- /dev/null +++ b/test/imp/test_imp_fmt.cpp @@ -0,0 +1,87 @@ +#define _CRT_SECURE_NO_WARNINGS +#include + +#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"; +} \ No newline at end of file