Created
August 21, 2020 01:39
-
-
Save alebastr/a98f7d50a096052e4fbaa90072ba939a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* CXXFLAGS += -std=c++17 | |
* CXXFLAGS += $(shell pkg-config --cflags catch2 fmt glibmm-2.4) | |
* LDFLAGS += $(shell pkg-config --libs catch2 fmt glibmm-2.4) | |
*/ | |
#define CATCH_CONFIG_MAIN | |
#define FMT_USE_INTERNAL | |
#include <catch2/catch.hpp> | |
#include <fmt/format.h> | |
#include <glibmm/ustring.h> | |
/* | |
* 1. Just print std::string. | |
* | |
* Broken because initializing char* with UTF-8 data does not make it | |
* contain UTF-8 data. | |
*/ | |
TEST_CASE("std::string formatter") { | |
std::string str = "魔法少女まどか★マギカ"; | |
auto result = fmt::format("{:.4}", str); | |
REQUIRE(result == "魔法少女"); | |
} | |
/* | |
* 2. Use UTF-8 string literals from The Standard. | |
* | |
* Broken before C++20 because why would it even work? UTF-8 string literal is | |
* char[N] under the hood which is not an UTF-8 string... | |
* | |
* Broken in C++20 because `u8` suddenly became `char8_t[N]` and requires | |
* reinterpret_cast for every real world interaction. | |
*/ | |
TEST_CASE("u8 formatter") { | |
REQUIRE(fmt::format(u8"{:.4}", u8"魔法少女まどか★マギカ") == u8"魔法少女"); | |
} | |
/* | |
* 3. Define custom formatter for Glib::ustring and use unicode-aware string | |
* APIs from glibmm. | |
* | |
* Works by copying tons of internals from fmt. Might work after reimplementing | |
* everything but requres even more internal knowledge. | |
*/ | |
template <> struct fmt::formatter<Glib::ustring> { | |
/* | |
* specs_ in formatter<std::string> is private, thus we have to reimplement | |
* the parser instead of simply inheriting from the right base | |
* | |
* copy some fmt internal code instead of reinventing the wheel. | |
*/ | |
template <typename ParseContext> | |
constexpr auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { | |
using handler_type = fmt::internal::dynamic_specs_handler<ParseContext>; | |
fmt::internal::specs_checker<handler_type> handler( | |
handler_type(specs_, ctx), fmt::internal::type::string_type); | |
auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); | |
fmt::internal::check_string_type_spec(specs_.type, ctx.error_handler()); | |
return it; | |
} | |
template <typename FormatContext> | |
auto format(const Glib::ustring &value, FormatContext &ctx) { | |
fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>( | |
specs_.width, specs_.width_ref, ctx); | |
fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>( | |
specs_.precision, specs_.precision_ref, ctx); | |
auto b = value; | |
if (specs_.precision >= 0 && (unsigned)specs_.precision < b.length()) { | |
if (specs_.precision == 0) { | |
specs_.precision++; | |
} | |
b = b.substr(0, specs_.precision); | |
auto diff = b.bytes() - specs_.precision; | |
if (specs_.width > 0) { | |
specs_.width += diff; | |
} | |
} | |
#if FMT_VERSION >= 70000 | |
return fmt::detail::write_bytes(ctx.out(), b.data(), specs_); | |
#else | |
using range_type = | |
fmt::internal::output_range<typename FormatContext::iterator, char>; | |
fmt::internal::basic_writer<range_type> writer(range_type(ctx.out())); | |
writer.write_bytes(b.data(), specs_); | |
return writer.out(); | |
#endif | |
} | |
private: | |
fmt::internal::dynamic_format_specs<char> specs_; | |
}; | |
TEST_CASE("Glib::ustring formatter") { | |
Glib::ustring str = "魔法少女まどか★マギカ"; | |
REQUIRE(fmt::format("{:.4}", str) == "魔法少女"); | |
REQUIRE(fmt::format("{:6.4}", str) == "魔法少女 "); | |
REQUIRE(fmt::format("{:<6.4}", str) == "魔法少女 "); | |
REQUIRE(fmt::format("{:^6.4}", str) == " 魔法少女 "); | |
REQUIRE(fmt::format("{:>6.4}", str) == " 魔法少女"); | |
REQUIRE(fmt::format("{:*<6.4}", str) == "魔法少女**"); | |
REQUIRE(fmt::format("{:*^6.4}", str) == "*魔法少女*"); | |
REQUIRE(fmt::format("{:*>6.4}", str) == "**魔法少女"); | |
} | |
/* | |
* 4. Use magic "C++20 will fix everything" char8_t type. | |
* | |
* Since `char8_t` (`enum unsigned char {}`) is a real UTF-8 character type, | |
* it's not compatible or interoperable with anything in the STL by design and | |
* not convertible to `char`. | |
* | |
* P0482R6 | |
* P1423R3 | |
*/ | |
std::basic_string_view<fmt::internal::char8_type> | |
operator""_u8(const char *data, size_t size) { | |
return {(const fmt::internal::char8_type *)data, size}; | |
} | |
TEST_CASE("fmt::char8_t formatter") { | |
auto str = "魔法少女まどか★マギカ"_u8; | |
REQUIRE(fmt::format("{:.4}"_u8, str) == "魔法少女"_u8); | |
REQUIRE(fmt::format("{:6.4}"_u8, str) == "魔法少女 "_u8); | |
REQUIRE(fmt::format("{:<6.4}"_u8, str) == "魔法少女 "_u8); | |
REQUIRE(fmt::format("{:^6.4}"_u8, str) == " 魔法少女 "_u8); | |
REQUIRE(fmt::format("{:>6.4}"_u8, str) == " 魔法少女"_u8); | |
REQUIRE(fmt::format("{:*<6.4}"_u8, str) == "魔法少女**"_u8); | |
REQUIRE(fmt::format("{:*^6.4}"_u8, str) == "*魔法少女*"_u8); | |
REQUIRE(fmt::format("{:*>6.4}"_u8, str) == "**魔法少女"_u8); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment