diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8d6e773 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.json] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.tsv] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74f38fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/tests/build +/tests/workdir/bin_* +/tests/.vs +/tests/.vscode/ipch +/tests/.clangd diff --git a/README.md b/README.md index e74af97..acff67c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ # C++ String Utils -C++17 and C++11 `std::string_view`-based utils. +C++17 and C++11 `std::string_view`-based UTF-8-based utils. +https://utf8everywhere.org/ ## `to_string` ```cpp -std::string buffer(32, '\0'); -std::string_view result = utils::to_string(12.34f, buffer); +u8string buffer(32, '\0'); +u8string_view result = utils::to_string(12.34f, buffer); assert(!result.empty()); assert(result == "12.34"); ``` ```cpp -std::string buffer(32, '\0'); -std::string_view result = utils::to_string(0xDEADBEEF, buffer, true); +u8string buffer(32, '\0'); +u8string_view result = utils::to_string(0xDEADBEEF, buffer, true); assert(!result.empty()); assert(result == "deadbeef"); ``` @@ -32,17 +33,18 @@ assert(u32 == 0xDEADBEEF); ## `trimm` ```cpp -std::string_view result = utils::trimm("\n 12.34 \t"); +u8string_view result = utils::trimm("\n 12.34 \t"); assert(result == "12.34"); ``` ## `split` ```cpp -utils::split("|12||34|5\\|6|", "|", [](std::string_view part, uint32_t idx) { +utils::split(u8"|12||🌍|34|5\\|6|", "|", [](u8string_view part, uint32_t idx) { switch (idx) { case 0: assert(part == "12"); break; - case 1: assert(part == "34"); break; - case 2: assert(part == "5\\|6"); break; + case 1: assert(part == u8"🌍"); break; + case 2: assert(part == "34"); break; + case 3: assert(part == "5\\|6"); break; default: assert(false); break; } }); @@ -50,12 +52,37 @@ utils::split("|12||34|5\\|6|", "|", [](std::string_view part, uint32_t idx) { ## `substr` ```cpp -constexpr std::string_view str = "user@email.com"; -uint32_t offset = 0; -std::string_view user = utils::substr(str, offset, "@"); +constexpr u8string_view str = "user@email.com"; +size_t offset = 0; +u8string_view user = utils::substr(str, offset, "@"); assert(user == "user"); -std::string_view email = utils::substr(str, offset, "."); +u8string_view email = utils::substr(str, offset, "."); assert(email == "email"); -std::string_view com = utils::substr(str, offset, "."); +u8string_view com = utils::substr(str, offset, "."); assert(com == "com"); ``` + +## conversion +```cpp +u8string u8str; +u16string u16str; +u32string u32str; + +u8str = u8"🌍"; +assert(u8str.size() == 4); +assert(u8str.length() == 1); +assert(u8str.sso_active()); + +u8str = u8"δ½ ε₯½ Hola Hello OlΓ‘ ΠŸΡ€ΠΈΠ²Π΅Ρ‚ こんにけは μ•ˆλ…•ν•˜μ„Έμš” Bonjour Hallo Ciao"; +assert(u8str.size() == 86); +assert(u8str.length() == 55); +assert(!u8str.sso_active()); + +u16str = u8str.toUtf16(); +assert(u16str.toUtf8() == u8str); +assert(u16str.toUtf32().toUtf8() == u8str); + +u32str = u8str.toUtf32(); +assert(u32str.toUtf8() == u8str); +assert(u32str.toUtf16().toUtf8() == u8str); +``` diff --git a/string_utils.hpp b/string_utils.hpp index fc8959b..d302f70 100644 --- a/string_utils.hpp +++ b/string_utils.hpp @@ -1,11 +1,12 @@ // C++ String Utils // -// C++17 and C++11 std::string_view-based utils. +// C++17 and C++11 std::string_view-based and UTF-8-based utils. // // Author: Yurii Blok // License: BSL-1.0 // https://github.com/yurablok/cpp-string-utils // History: +// v0.7 2024-May-12 Added `u8`,`u16`,`u32`... `string` and `string_view`. // v0.6 2023-Apr-26 Fixed build with clang-cl. Fixed `substr` [2]. // v0.5 2023-Feb-14 Fixed `substr`. // v0.4 2023-Feb-09 Added `checked_string_view`. @@ -52,6 +53,9 @@ # include "string_view.hpp" // https://github.com/martinmoene/string-view-lite namespace std { using string_view = nonstd::string_view; + using wstring_view = nonstd::wstring_view; + using u16string_view = nonstd::u16string_view; + using u32string_view = nonstd::u32string_view; } inline std::string& operator+=(std::string& a, const std::string_view b) { a.insert(a.end(), b.cbegin(), b.cend()); @@ -63,48 +67,244 @@ inline std::string& operator+=(std::string& a, const std::string_view b) { # include #endif -namespace utils { +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) +# pragma warning(disable: 4244) +#endif // _MSC_VER +#ifndef TINY_UTF8_NOEXCEPT +# define TINY_UTF8_NOEXCEPT +#endif +#include "tinyutf8.h" // https://github.com/DuffsDevice/tiny-utf8 +#ifdef _MSC_VER +# pragma warning(pop) +#endif // _MSC_VER + +static_assert(static_cast(u8"🌍"[0]) == 0xF0, "Wrong UTF-8 config"); +static_assert(static_cast(u8"🌍"[1]) == 0x9F, "Wrong UTF-8 config"); +static_assert(static_cast(u8"🌍"[2]) == 0x8C, "Wrong UTF-8 config"); +static_assert(static_cast(u8"🌍"[3]) == 0x8D, "Wrong UTF-8 config"); + + +#if defined(__cpp_char8_t) +static_assert(sizeof(char) >= sizeof(char8_t), ""); +#endif + +class u8string; +class u16string; +class u32string; -class checked_string_view : public std::string_view { + +class u8string_view : public std::string_view { public: - _CONSTEXPR17 checked_string_view() - : std::string_view() {} - _CONSTEXPR17 checked_string_view(const checked_string_view& str) - : std::string_view(str) {} - _CONSTEXPR17 checked_string_view(checked_string_view&& str) - : std::string_view(std::move(str)) {} - _CONSTEXPR17 checked_string_view(const char* str) - : std::string_view(str == nullptr ? "" : str) {} - _CONSTEXPR17 checked_string_view(const char* str, size_t size) - : std::string_view(str == nullptr ? "" : str, size) {} - inline checked_string_view(const std::string& str) - : std::string_view(str.c_str(), str.size()) {} - _CONSTEXPR17 checked_string_view(const std::string_view str) - : std::string_view(str) {} - - template::value, bool>::type = true> - inline checked_string_view(const string_t& str) - : std::string_view( - reinterpret_cast(str.c_str()), str.size()) {} - - template::value, bool>::type = true> - _CONSTEXPR17 checked_string_view(const string_t str) - : std::string_view(str == nullptr ? "" - : reinterpret_cast(str)) {} - - template::value, bool>::type = true> - _CONSTEXPR17 checked_string_view(const string_t str, size_t size) - : std::string_view(str == nullptr ? "" - : reinterpret_cast(str), size) {} - - using std::string_view::operator=; + constexpr u8string_view() noexcept = default; + template + constexpr u8string_view(ch_t str) noexcept + : std::string_view(str == nullptr ? "" : reinterpret_cast(str)) {} + template + constexpr u8string_view(ch_t str, size_t size) noexcept + : std::string_view(str == nullptr ? "" : reinterpret_cast(str), size) {} + template + constexpr u8string_view(const char (&str)[size]) noexcept + : std::string_view(str, size - 1) {} + _CONSTEXPR17 u8string_view(const std::string& str) noexcept; + constexpr u8string_view(const std::string_view& str) noexcept; + inline u8string_view(const u8string& str) noexcept; + constexpr u8string_view(const u8string_view& str) noexcept = default; +# if defined(__cpp_char8_t) + constexpr u8string_view(const std::u8string& str) noexcept; + constexpr u8string_view(const std::u8string_view& str) noexcept; + template + constexpr u8string_view(const char8_t (&str)[size]) noexcept + : std::string_view(reinterpret_cast(str), size - 1) {} +# endif + + template + constexpr bool operator==(ch_t str) const noexcept { + return compare(str == nullptr ? "" : reinterpret_cast(str)) == 0; + } + template + constexpr bool operator==(const char (&str)[size]) const noexcept { + return compare(u8string_view(str, size - 1)) == 0; + } + _CONSTEXPR17 bool operator==(const std::string& str) const noexcept; + _CONSTEXPR17 bool operator==(const std::string_view& str) const noexcept; + inline bool operator==(const u8string& str) const noexcept; + _CONSTEXPR17 bool operator==(const u8string_view& str) const noexcept; +# if defined(__cpp_char8_t) + inline bool operator==(const std::u8string& str) const noexcept; + constexpr bool operator==(const std::u8string_view& str) const noexcept; + template + constexpr bool operator==(const char8_t (&str)[size]) const noexcept { + return compare(u8string_view(str, size - 1)) == 0; + } +# endif + template + inline bool operator!=(str_t str) const noexcept { + return !operator==(str); + } + + // Little-endian + inline u16string toUtf16(const bool strict = true) const; + // Little-endian + inline u32string toUtf32() const; +}; // class u8string_view + +class u8string : public tiny_utf8::string { +public: + inline u8string() = default; + + template + inline u8string(ch_t str) noexcept + : tiny_utf8::string(str == nullptr ? "" : reinterpret_cast(str)) {} + + template + inline u8string(ch_t str, size_t size, enable_if_ptr* = {}) noexcept + : tiny_utf8::string(str == nullptr ? "" : reinterpret_cast(str), size) {} + + template + inline u8string(const char (&str)[size]) noexcept + : tiny_utf8::string(str, size - 1) {} + + inline u8string(const std::string& str) noexcept; + inline u8string(const std::string_view& str) noexcept; + inline u8string(const u8string& str) noexcept = default; + inline u8string(const u8string_view& str) noexcept; + inline u8string(const size_t count, const char ch) noexcept; +# if defined(__cpp_char8_t) + inline u8string(const std::u8string& str) noexcept; + inline u8string(const std::u8string_view& str) noexcept; + template + inline u8string(ch_t str, size_t size, enable_if_ptr* = {}) noexcept + : tiny_utf8::string(str == nullptr ? "" : reinterpret_cast(str), size) {} + template + inline u8string(const char8_t (&str)[size]) noexcept + : tiny_utf8::string(reinterpret_cast(str), size - 1) {} +# endif + + template + inline bool operator==(ch_t str) const noexcept { + return compare(str == nullptr ? "" : reinterpret_cast(str)) == 0; + } + template + inline bool operator==(const char (&str)[size]) const noexcept { + return u8string_view(*this) == u8string_view(str, size - 1); + } + inline bool operator==(const std::string& str) const noexcept; + inline bool operator==(const std::string_view& str) const noexcept; + inline bool operator==(const u8string& str) const noexcept; + inline bool operator==(const u8string_view& str) const noexcept; +# if defined(__cpp_char8_t) + template + inline bool operator==(const char8_t (&str)[size]) const noexcept { + return u8string_view(*this) == u8string_view(str, size - 1); + } + inline bool operator==(const std::u8string& str) const noexcept; + inline bool operator==(const std::u8string_view& str) const noexcept; +# endif + template + inline bool operator!=(str_t str) const noexcept { + return !operator==(str); + } + + template + inline u8string& operator+=(ch_t str) noexcept { + if (str != nullptr) { + append(reinterpret_cast(str)); + } + return *this; + } + template + inline u8string& operator+=(const char (&str)[size]) noexcept { + append(std::string(str, size - 1)); //TODO: Extra copy + return *this; + } + inline u8string& operator+=(const std::string& str) noexcept; + inline u8string& operator+=(const std::string_view& str) noexcept; + inline u8string& operator+=(const u8string& str) noexcept; + inline u8string& operator+=(const u8string_view& str) noexcept; +# if defined(__cpp_char8_t) + inline u8string& operator+=(const std::u8string& str) noexcept; + inline u8string& operator+=(const std::u8string_view& str) noexcept; + template + inline u8string& operator+=(const char8_t (&str)[size]) noexcept { + append(std::string(reinterpret_cast(str), size - 1)); //TODO: Extra copy + return *this; + } +# endif + + // Little-endian + inline u16string toUtf16(const bool strict = true) const; + // Little-endian + inline u32string toUtf32() const; +}; // class u8string + + +class u16string_view +# if WCHAR_MAX < 0x10000 + : public std::wstring_view { +public: + using std::wstring_view::basic_string_view; +# else + : public std::u16string_view { +public: + using std::u16string_view::basic_string_view; +# endif + inline u16string_view(const u16string& str) noexcept; + inline u8string toUtf8(const bool strict = true) const; + // Little-endian + inline u32string toUtf32(const bool strict = true) const; +}; + + +class u16string +# if WCHAR_MAX < 0x10000 + : public std::wstring { +# else + : public std::u16string { +# endif +public: + inline u8string toUtf8(const bool strict = true) const; + // Little-endian + inline u32string toUtf32(const bool strict = true) const; +}; + + +class u32string_view +# if WCHAR_MAX < 0x10000 + : public std::u32string_view { +public: + using std::u32string_view::basic_string_view; +# else + : public std::wstring_view { +public: + using std::wstring_view::basic_string_view; +# endif + inline u32string_view(const u32string& str) noexcept; + inline u8string toUtf8(const bool strict = true) const; + // Little-endian + inline u16string toUtf16(const bool strict = true) const; +}; + + +class u32string +# if WCHAR_MAX < 0x10000 + : public std::u32string { +# else + : public std::wstring { +# endif +public: + inline u8string toUtf8(const bool strict = true) const; + // Little-endian + inline u16string toUtf16(const bool strict = true) const; }; -inline std::string_view trimm(checked_string_view string, - const checked_string_view by = std::string_view("\t\n\r \0", 5)) noexcept { + +namespace utils { + + +inline u8string_view trimm(u8string_view string, + const std::string_view by = std::string_view("\t\n\r \0", 5)) noexcept { while (!string.empty()) { if (by.find(string.front()) == std::string_view::npos) { break; @@ -120,8 +320,8 @@ inline std::string_view trimm(checked_string_view string, return string; } -inline void split(const checked_string_view str, const checked_string_view by, - const std::function handler, +inline void split(const u8string_view str, const std::string_view by, + const std::function handler, const bool withEmpty = false, const char escape = '\\') noexcept { if (by.empty() || !handler) { return; @@ -143,20 +343,20 @@ inline void split(const checked_string_view str, const checked_string_view by, if (by.find(str[i]) == std::string_view::npos) { continue; } - const std::string_view part = str.substr(begin, i - begin); + const u8string_view part = str.substr(begin, i - begin); if (withEmpty || !part.empty()) { handler(part, idx++); } begin = i + 1; } - const std::string_view part = str.substr(begin); + const u8string_view part = str.substr(begin); if (!part.empty()) { handler(part, idx); } } -inline std::string_view substr(const checked_string_view str, size_t& offset, - const checked_string_view split_by, +inline u8string_view substr(const u8string_view str, size_t& offset, + const std::string_view split_by, const bool withEmpty = false, const char escape = '\\') noexcept { if (split_by.empty()) { return {}; @@ -180,14 +380,14 @@ inline std::string_view substr(const checked_string_view str, size_t& offset, if (split_by.find(str[offset]) == std::string_view::npos) { continue; } - const std::string_view part = str.substr(begin, offset - begin); + const u8string_view part = str.substr(begin, offset - begin); if (withEmpty || !part.empty()) { ++offset; return part; } begin = offset + 1; } - const std::string_view part = str.substr(begin); + const u8string_view part = str.substr(begin); ++offset; if (!part.empty()) { return part; @@ -195,8 +395,8 @@ inline std::string_view substr(const checked_string_view str, size_t& offset, return {}; } -inline void parseCSV(const checked_string_view csv, - const std::function onCell, +inline void parseCSV(const u8string_view csv, + const std::function onCell, const std::function onEndl = nullptr) { if (!onCell) { return; @@ -275,7 +475,7 @@ template::value, bool >::type = true> -inline std::string_view to_string(const integer_t number, const std::string_view buffer, +inline u8string_view to_string(const integer_t number, const u8string_view buffer, const bool hex = false) noexcept { const auto [ptr, ec] = std::to_chars( const_cast(buffer.data()), @@ -291,7 +491,7 @@ inline std::string_view to_string(const integer_t number, const std::string_view #else // !CPP_STRING_UTILS_LIB_CHARCONV -inline std::string_view to_string(const int8_t number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const int8_t number, const u8string_view buffer) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), "%" PRIi8, number); if (length <= 0) { @@ -299,7 +499,7 @@ inline std::string_view to_string(const int8_t number, const std::string_view bu } return buffer.substr(0, length); } -inline std::string_view to_string(const uint8_t number, const std::string_view buffer, +inline u8string_view to_string(const uint8_t number, const u8string_view buffer, const bool hex = false) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), hex ? "%" PRIx8 : "%" PRIu8, number); @@ -308,7 +508,7 @@ inline std::string_view to_string(const uint8_t number, const std::string_view b } return buffer.substr(0, length); } -inline std::string_view to_string(const int16_t number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const int16_t number, const u8string_view buffer) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), "%" PRIi16, number); if (length <= 0) { @@ -316,7 +516,7 @@ inline std::string_view to_string(const int16_t number, const std::string_view b } return buffer.substr(0, length); } -inline std::string_view to_string(const uint16_t number, const std::string_view buffer, +inline u8string_view to_string(const uint16_t number, const u8string_view buffer, const bool hex = false) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), hex ? "%" PRIx16 : "%" PRIu16, number); @@ -325,7 +525,7 @@ inline std::string_view to_string(const uint16_t number, const std::string_view } return buffer.substr(0, length); } -inline std::string_view to_string(const int32_t number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const int32_t number, const u8string_view buffer) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), "%" PRIi32, number); if (length <= 0) { @@ -333,7 +533,7 @@ inline std::string_view to_string(const int32_t number, const std::string_view b } return buffer.substr(0, length); } -inline std::string_view to_string(const uint32_t number, const std::string_view buffer, +inline u8string_view to_string(const uint32_t number, const u8string_view buffer, const bool hex = false) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), hex ? "%" PRIx32 : "%" PRIu32, number); @@ -342,7 +542,7 @@ inline std::string_view to_string(const uint32_t number, const std::string_view } return buffer.substr(0, length); } -inline std::string_view to_string(const int64_t number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const int64_t number, const u8string_view buffer) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), "%" PRIi64, number); if (length <= 0) { @@ -350,7 +550,7 @@ inline std::string_view to_string(const int64_t number, const std::string_view b } return buffer.substr(0, length); } -inline std::string_view to_string(const uint64_t number, const std::string_view buffer, +inline u8string_view to_string(const uint64_t number, const u8string_view buffer, const bool hex = false) noexcept { const int32_t length = std::snprintf(const_cast( buffer.data()), buffer.size(), hex ? "%" PRIx64 : "%" PRIu64, number); @@ -369,7 +569,7 @@ template::value, bool >::type = true, typename _ = bool> -inline std::string_view to_string(const floating_t number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const floating_t number, const u8string_view buffer) noexcept { const auto [ptr, ec] = std::to_chars( const_cast(buffer.data()), const_cast(buffer.data() + buffer.size()), @@ -383,7 +583,7 @@ inline std::string_view to_string(const floating_t number, const std::string_vie #else // !CPP_STRING_UTILS_LIB_CHARCONV_FLOAT -inline std::string_view to_string(const float number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const float number, const u8string_view buffer) noexcept { const int32_t length = std::trunc(number) == number ? std::snprintf(const_cast( buffer.data()), buffer.size(), "%.0f", number) @@ -394,7 +594,7 @@ inline std::string_view to_string(const float number, const std::string_view buf } return trimm(buffer.substr(0, length), "0\0"); } -inline std::string_view to_string(const double number, const std::string_view buffer) noexcept { +inline u8string_view to_string(const double number, const u8string_view buffer) noexcept { const int32_t length = std::trunc(number) == number ? std::snprintf(const_cast( buffer.data()), buffer.size(), "%.0f", number) @@ -414,7 +614,7 @@ inline std::string_view to_string(const double number, const std::string_view bu template, bool> = true> -inline bool from_string(const checked_string_view string, integer_t& number, +inline bool from_string(const u8string_view string, integer_t& number, const bool hex = false) noexcept { auto [ptr, ec] = std::from_chars( string.data(), @@ -431,7 +631,7 @@ inline bool from_string(const checked_string_view string, integer_t& number, #else // !CPP_STRING_UTILS_LIB_CHARCONV -inline bool from_string(const checked_string_view string, int8_t& number) noexcept { +inline bool from_string(const u8string_view string, int8_t& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%u" SCNi8, static_cast(string.size())); @@ -440,7 +640,7 @@ inline bool from_string(const checked_string_view string, int8_t& number) noexce } return true; } -inline bool from_string(const checked_string_view string, uint8_t& number, +inline bool from_string(const u8string_view string, uint8_t& number, const bool hex = false) noexcept { char format[8]; std::snprintf(format, sizeof(format), hex ? "%%%u" SCNx8 : "%%%u" SCNu8, @@ -450,7 +650,7 @@ inline bool from_string(const checked_string_view string, uint8_t& number, } return true; } -inline bool from_string(const checked_string_view string, int16_t& number) noexcept { +inline bool from_string(const u8string_view string, int16_t& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%u" SCNi16, static_cast(string.size())); @@ -459,7 +659,7 @@ inline bool from_string(const checked_string_view string, int16_t& number) noexc } return true; } -inline bool from_string(const checked_string_view string, uint16_t& number, +inline bool from_string(const u8string_view string, uint16_t& number, const bool hex = false) noexcept { char format[8]; std::snprintf(format, sizeof(format), hex ? "%%%u" SCNx16 : "%%%u" SCNu16, @@ -469,7 +669,7 @@ inline bool from_string(const checked_string_view string, uint16_t& number, } return true; } -inline bool from_string(const checked_string_view string, int32_t& number) noexcept { +inline bool from_string(const u8string_view string, int32_t& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%u" SCNi32, static_cast(string.size())); @@ -478,7 +678,7 @@ inline bool from_string(const checked_string_view string, int32_t& number) noexc } return true; } -inline bool from_string(const checked_string_view string, uint32_t& number, +inline bool from_string(const u8string_view string, uint32_t& number, const bool hex = false) noexcept { char format[8]; std::snprintf(format, sizeof(format), hex ? "%%%u" SCNx32 : "%%%u" SCNu32, @@ -488,7 +688,7 @@ inline bool from_string(const checked_string_view string, uint32_t& number, } return true; } -inline bool from_string(const checked_string_view string, int64_t& number) noexcept { +inline bool from_string(const u8string_view string, int64_t& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%u" SCNi64, static_cast(string.size())); @@ -497,7 +697,7 @@ inline bool from_string(const checked_string_view string, int64_t& number) noexc } return true; } -inline bool from_string(const checked_string_view string, uint64_t& number, +inline bool from_string(const u8string_view string, uint64_t& number, const bool hex = false) noexcept { char format[8]; std::snprintf(format, sizeof(format), hex ? "%%%u" SCNx64 : "%%%u" SCNu64, @@ -514,7 +714,7 @@ inline bool from_string(const checked_string_view string, uint64_t& number, template, bool> = true> -inline bool from_string(const checked_string_view string, floating_t& number) noexcept { +inline bool from_string(const u8string_view string, floating_t& number) noexcept { auto [ptr, ec] = std::from_chars( string.data(), string.data() + string.size(), @@ -528,7 +728,7 @@ inline bool from_string(const checked_string_view string, floating_t& number) no #else // !CPP_STRING_UTILS_LIB_CHARCONV_FLOAT -inline bool from_string(const checked_string_view string, float& number) noexcept { +inline bool from_string(const u8string_view string, float& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%uf", static_cast(string.size())); @@ -537,7 +737,7 @@ inline bool from_string(const checked_string_view string, float& number) noexcep } return true; } -inline bool from_string(const checked_string_view string, double& number) noexcept { +inline bool from_string(const u8string_view string, double& number) noexcept { char format[8]; std::snprintf(format, sizeof(format), "%%%ulf", static_cast(string.size())); @@ -551,4 +751,454 @@ inline bool from_string(const checked_string_view string, double& number) noexce } // namespace utils + + + +_CONSTEXPR17 u8string_view::u8string_view(const std::string& str) noexcept + : std::string_view(str.data(), str.size()) {} + +constexpr u8string_view::u8string_view(const std::string_view& str) noexcept + : std::string_view(str) {} + +inline u8string_view::u8string_view(const u8string& str) noexcept + : std::string_view(str.data(), str.size()) {} + +#if defined(__cpp_char8_t) +constexpr u8string_view::u8string_view(const std::u8string& str) noexcept + : std::string_view(str.empty() ? "" : reinterpret_cast(str.data()), str.size()) {} + +constexpr u8string_view::u8string_view(const std::u8string_view& str) noexcept + : std::string_view(str.empty() ? "" : reinterpret_cast(str.data()), str.size()) {} +#endif + + +_CONSTEXPR17 bool u8string_view::operator==(const std::string& str) const noexcept { + return compare(str) == 0; +} +_CONSTEXPR17 bool u8string_view::operator==(const std::string_view& str) const noexcept { + return compare(str) == 0; +} +inline bool u8string_view::operator==(const u8string& str) const noexcept { + return compare(u8string_view(str)) == 0; +} +_CONSTEXPR17 bool u8string_view::operator==(const u8string_view& str) const noexcept { + return compare(str) == 0; +} + +#if defined(__cpp_char8_t) +inline bool u8string_view::operator==(const std::u8string& str) const noexcept { + return compare(reinterpret_cast(str.c_str())) == 0; +} +constexpr bool u8string_view::operator==(const std::u8string_view& str) const noexcept { + return !str.empty() && compare(reinterpret_cast(str.data())) == 0; +} +#endif + + +inline u8string::u8string(const std::string& str) noexcept + : tiny_utf8::string(str) {} + +inline u8string::u8string(const std::string_view& str) noexcept + : tiny_utf8::string(std::string(str)) {} + +inline u8string::u8string(const u8string_view& str) noexcept + : tiny_utf8::string(str.data(), str.size()) {} + +inline u8string::u8string(const size_t count, const char ch) noexcept + : tiny_utf8::string(count, ch) {} + +#if defined(__cpp_char8_t) +inline u8string::u8string(const std::u8string& str) noexcept + : tiny_utf8::string(reinterpret_cast(str.data()), str.size()) {} + +inline u8string::u8string(const std::u8string_view& str) noexcept + : tiny_utf8::string(reinterpret_cast(str.data()), str.size()) {} +#endif + + +inline bool u8string::operator==(const std::string& str) const noexcept { + return u8string_view(*this) == str; +} +inline bool u8string::operator==(const std::string_view& str) const noexcept { + return u8string_view(*this) == str; +} +inline bool u8string::operator==(const u8string& str) const noexcept { + return compare(str) == 0; +} +inline bool u8string::operator==(const u8string_view& str) const noexcept { + return u8string_view(*this) == str; +} +#if defined(__cpp_char8_t) +inline bool u8string::operator==(const std::u8string& str) const noexcept { + return u8string_view(*this) == u8string_view(reinterpret_cast(str.data()), str.size()); +} +inline bool u8string::operator==(const std::u8string_view& str) const noexcept { + return u8string_view(*this) == u8string_view(reinterpret_cast(str.data()), str.size()); +} +#endif + + +inline u8string& u8string::operator+=(const std::string& str) noexcept { + append(str); + return *this; +} +inline u8string& u8string::operator+=(const std::string_view& str) noexcept { + append(std::string(str)); //TODO: Extra copy + return *this; +} +inline u8string& u8string::operator+=(const u8string& str) noexcept { + append(str); + return *this; +} +inline u8string& u8string::operator+=(const u8string_view& str) noexcept { + append(std::string(str)); //TODO: Extra copy + return *this; +} + +#if defined(__cpp_char8_t) +inline u8string& u8string::operator+=(const std::u8string& str) noexcept { + append(std::string(reinterpret_cast(str.data()), str.size())); //TODO: Extra copy + return *this; +} +inline u8string& u8string::operator+=(const std::u8string_view& str) noexcept { + append(std::string(reinterpret_cast(str.data()), str.size())); //TODO: Extra copy + return *this; +} +#endif + + +inline u16string u8string_view::toUtf16(const bool strict) const { + constexpr uint32_t halfShift = 10; // used for shifting by 10 bits + constexpr uint32_t halfBase = 0x00010000; + constexpr uint32_t halfMask = 0x000003FF; + constexpr uint32_t UNI_REPLACEMENT_CHAR = 0x0000FFFD; + constexpr uint32_t UNI_MAX_BMP = 0x0000FFFF; + constexpr uint32_t UNI_MAX_LEGAL_UTF32 = 0x0010FFFF; + constexpr uint32_t UNI_SUR_HIGH_START = 0x0000D800; + constexpr uint32_t UNI_SUR_LOW_START = 0x0000DC00; + constexpr uint32_t UNI_SUR_LOW_END = 0x0000DFFF; + u16string res; + for (size_t i = 0; i < size();) { + uint32_t width = 0; + uint32_t ch = 0; + // 1 - 0xxxxxxx + if ((data()[i] & 0x80) == 0x00) { + width = 1; + ch = data()[i]; + } + // 2 - 110xxxxx 10xxxxxx + else if ((data()[i] & 0xE0) == 0xC0) { + width = 2; + ch = ((data()[i] & 0x1F) << 6) + | (data()[i + 1] & 0x3F); + } + // 3 - 1110xxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xF0) == 0xE0) { + width = 3; + ch = ((data()[i] & 0x0F) << 12) + | ((data()[i + 1] & 0x3F) << 6) + | (data()[i + 2] & 0x3F); + } + // 4 - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xF8) == 0xF0) { + width = 4; + ch = ((data()[i] & 0x07) << 18) + | ((data()[i + 1] & 0x3F) << 12) + | ((data()[i + 2] & 0x3F) << 6) + | (data()[i + 3] & 0x3F); + } + // 5 - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xFC) == 0xF8) { + width = 5; + ch = ((data()[i] & 0x03) << 24) + | ((data()[i + 1] & 0x3F) << 18) + | ((data()[i + 2] & 0x3F) << 12) + | ((data()[i + 3] & 0x3F) << 6) + | (data()[i + 4] & 0x3F); + } + else { + // Invalid UTF-8 value + break; + } + if (width + i > size()) { + // Unexpected endl + break; + } + // Not checked for 10xxxxxx + i += width; + + if (ch <= UNI_MAX_BMP) { // Target is a character <= 0xFFFF + // UTF-16 surrogate values are illegal in UTF-32; + // 0xffff or 0xfffe are both reserved values + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (strict) { + res.push_back('\4'); + break; + } + else { + res.push_back(static_cast(UNI_REPLACEMENT_CHAR)); + } + } + else { + res.push_back(static_cast(ch)); // normal case + } + } + else if (ch > UNI_MAX_LEGAL_UTF32) { + if (strict) { + res.push_back('\4'); + //TODO: break? throw? + } + else { + res.push_back(static_cast(UNI_REPLACEMENT_CHAR)); + } + } + else { + ch -= halfBase; + res.push_back(static_cast((ch >> halfShift) + UNI_SUR_HIGH_START)); + res.push_back(static_cast((ch & halfMask) + UNI_SUR_LOW_START)); + } + } + return res; +} + +inline u32string u8string_view::toUtf32() const { + u32string res; + for (size_t i = 0; i < size();) { + uint32_t width = 0; + uint32_t ch = 0; + // 1 - 0xxxxxxx + if ((data()[i] & 0x80) == 0x00) { + width = 1; + ch = data()[i]; + } + // 2 - 110xxxxx 10xxxxxx + else if ((data()[i] & 0xE0) == 0xC0) { + width = 2; + ch = ((data()[i] & 0x1F) << 6) + | (data()[i + 1] & 0x3F); + } + // 3 - 1110xxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xF0) == 0xE0) { + width = 3; + ch = ((data()[i] & 0x0F) << 12) + | ((data()[i + 1] & 0x3F) << 6) + | (data()[i + 2] & 0x3F); + } + // 4 - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xF8) == 0xF0) { + width = 4; + ch = ((data()[i] & 0x07) << 18) + | ((data()[i + 1] & 0x3F) << 12) + | ((data()[i + 2] & 0x3F) << 6) + | (data()[i + 3] & 0x3F); + } + // 5 - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + else if ((data()[i] & 0xFC) == 0xF8) { + width = 5; + ch = ((data()[i] & 0x03) << 24) + | ((data()[i + 1] & 0x3F) << 18) + | ((data()[i + 2] & 0x3F) << 12) + | ((data()[i + 3] & 0x3F) << 6) + | (data()[i + 4] & 0x3F); + } + else { + // Invalid UTF-8 value + break; + } + if (width + i > size()) { + // Unexpected endl + break; + } + // Not checked for 10xxxxxx + i += width; + + res.push_back(ch); + } + return res; +} + +inline u16string u8string::toUtf16(const bool strict) const { + return u8string_view(*this).toUtf16(strict); +} + +inline u32string u8string::toUtf32() const { + return u8string_view(*this).toUtf32(); +} + +u16string_view::u16string_view(const u16string& str) noexcept + : u16string_view(str.c_str(), str.size()) {} + +inline u8string u16string_view::toUtf8(const bool strict) const { + constexpr uint32_t halfShift = 10; // used for shifting by 10 bits + constexpr uint32_t halfBase = 0x00010000; + constexpr uint32_t UNI_SUR_HIGH_START = 0x0000D800; + constexpr uint32_t UNI_SUR_HIGH_END = 0x0000DBFF; + constexpr uint32_t UNI_SUR_LOW_START = 0x0000DC00; + constexpr uint32_t UNI_SUR_LOW_END = 0x0000DFFF; + u8string res; + uint32_t ch, ch2; + for (size_t i = 0; i < size(); ++i) { + ch = data()[i]; + // If we have a surrogate pair, convert to UTF32 first. + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + // If the 16 bits following the high surrogate are in the source buffer... + if (i + 1 < size()) { + ch2 = data()[++i]; + // If it's a low surrogate, convert to UTF32. + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + } + // it's an unpaired high surrogate + else if (strict) { + res.push_back('\4'); + break; + } + } + else { // We don't have the 16 bits following the high surrogate. + res.push_back('\4'); + break; + } + } + else if (strict) { + // UTF-16 surrogate values are illegal in UTF-32 + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + res.push_back('\4'); + break; + } + } + res.push_back(ch); + } + return res; +} + +inline u32string u16string_view::toUtf32(const bool strict) const { + constexpr uint32_t halfShift = 10; // used for shifting by 10 bits + constexpr uint32_t halfBase = 0x00010000; + constexpr uint32_t UNI_SUR_HIGH_START = 0x0000D800; + constexpr uint32_t UNI_SUR_HIGH_END = 0x0000DBFF; + constexpr uint32_t UNI_SUR_LOW_START = 0x0000DC00; + constexpr uint32_t UNI_SUR_LOW_END = 0x0000DFFF; + u32string res; + uint32_t ch, ch2; + for (uint32_t i = 0; i < size(); ++i) { + ch = data()[i]; + // If we have a surrogate pair, convert to UTF32 first. + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) { + // If the 16 bits following the high surrogate are in the source buffer... + if (i + 1 < size()) { + ch2 = data()[++i]; + // If it's a low surrogate, convert to UTF32. + if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) { + ch = ((ch - UNI_SUR_HIGH_START) << halfShift) + + (ch2 - UNI_SUR_LOW_START) + halfBase; + } + // it's an unpaired high surrogate + else if (strict) { + res.push_back('\4'); + break; + } + } + else { // We don't have the 16 bits following the high surrogate. + res.push_back('\4'); + break; + } + } + else if (strict) { + // UTF-16 surrogate values are illegal in UTF-32 + if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) { + res.push_back('\4'); + break; + } + } + res.push_back(ch); + } + return res; +} + +inline u8string u16string::toUtf8(const bool strict) const { + return u16string_view(*this).toUtf8(strict); +} + +inline u32string u16string::toUtf32(const bool strict) const { + return u16string_view(*this).toUtf32(strict); +} + +u32string_view::u32string_view(const u32string& str) noexcept + : u32string_view(str.c_str(), str.size()) {} + +inline u8string u32string_view::toUtf8(const bool strict) const { + constexpr uint32_t UNI_SUR_HIGH_START = 0x0000D800; + constexpr uint32_t UNI_SUR_LOW_END = 0x0000DFFF; + u8string res; + for (uint32_t i = 0; i < size(); ++i) { + uint32_t ch = data()[i]; + if (strict) { + // UTF-16 surrogate values are illegal in UTF-32 + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + res.push_back('\4'); + break; + } + } + res.push_back(ch); + } + return res; +} + +inline u16string u32string_view::toUtf16(const bool strict) const { + constexpr uint32_t halfShift = 10; // used for shifting by 10 bits + constexpr uint32_t halfBase = 0x00010000; + constexpr uint32_t halfMask = 0x000003FF; + constexpr uint32_t UNI_REPLACEMENT_CHAR = 0x0000FFFD; + constexpr uint32_t UNI_MAX_BMP = 0x0000FFFF; + constexpr uint32_t UNI_MAX_LEGAL_UTF32 = 0x0010FFFF; + constexpr uint32_t UNI_SUR_HIGH_START = 0x0000D800; + constexpr uint32_t UNI_SUR_LOW_START = 0x0000DC00; + constexpr uint32_t UNI_SUR_LOW_END = 0x0000DFFF; + u16string res; + for (size_t i = 0; i < size(); ++i) { + uint32_t ch = data()[i]; + if (ch <= UNI_MAX_BMP) { // Target is a character <= 0xFFFF + // UTF-16 surrogate values are illegal in UTF-32; + // 0xffff or 0xfffe are both reserved values + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (strict) { + res.push_back('\4'); + break; + } + else { + res.push_back(static_cast(UNI_REPLACEMENT_CHAR)); + } + } + else { + res.push_back(static_cast(ch)); // normal case + } + } + else if (ch > UNI_MAX_LEGAL_UTF32) { + if (strict) { + res.push_back('\4'); + //TODO: break? throw? + } + else { + res.push_back(static_cast(UNI_REPLACEMENT_CHAR)); + } + } + else { + ch -= halfBase; + res.push_back(static_cast((ch >> halfShift) + UNI_SUR_HIGH_START)); + res.push_back(static_cast((ch & halfMask) + UNI_SUR_LOW_START)); + } + } + return res; +} + +inline u8string u32string::toUtf8(const bool strict) const { + return u32string_view(*this).toUtf8(strict); +} + +inline u16string u32string::toUtf16(const bool strict) const { + return u32string_view(*this).toUtf16(strict); +} + + #endif // CPP_STRING_UTILS diff --git a/tests/.vscode/settings.json b/tests/.vscode/settings.json new file mode 100644 index 0000000..82415fd --- /dev/null +++ b/tests/.vscode/settings.json @@ -0,0 +1,59 @@ +{ + "C_Cpp.intelliSenseCachePath": "${workspaceFolder}/.vscode", + //"C_Cpp.intelliSenseCacheSize": 0, // If disk storage is slow + "C_Cpp.autoAddFileAssociations": false, + + "editor.autoIndent": "advanced", + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.rulers": [ 80, 100 ], + //"editor.cursorStyle": "block", + "editor.minimap.enabled": false, + "[Log]": { "editor.wordWrap": "on" }, + + "files.trimTrailingWhitespace": true, + "[markdown]": { + "files.trimTrailingWhitespace": false + }, + "files.trimFinalNewlines": true, + + "explorer.autoReveal": false, + "scm.autoReveal": false, + "scm.defaultViewMode": "tree", + + "cmake.useCMakePresets": "auto", + "cmake.configureOnOpen": true, + + //"cmake.parallelJobs": 1, // 0 - not working + + "cmake.configureSettings": { + "CMAKE_BUILD_TYPE": "${buildType}", + "CMAKE_CONFIGURATION_TYPES": "${buildType}" + }, + "cmake.setBuildTypeOnMultiConfig": true, + //"cmake.buildTask": true, + + "cmake.buildDirectory": "${workspaceFolder}/build/.cmake/${buildType}-${generator}", + "cmake.debugConfig": { + "cwd": "${workspaceFolder}/workdir", + "args": [ "--test-arg" ], + //"MIMode": "lldb", //NOTE: on MacOS + "externalConsole": false, + "logging": { "programOutput": true, "moduleLoad": false } + }, + "cmake.options.statusBarVisibility": "visible", + "cmake.options.advanced": { + "buildPreset": { "statusBarVisibility": "hidden" }, + "ctest": { "statusBarVisibility": "icon" }, + "cpack": { "statusBarVisibility": "icon" }, + "workflowPreset": { "statusBarVisibility": "hidden" }, + "workflow": { "statusBarVisibility": "hidden" } + }, + "cmake.statusbar.advanced": { // For old compatibility + "status": { "visibility": "compact" }, + //"kit": { "visibility": "compact" }, + // workdir can't be configured + "launch": { "visibility": "hidden" }, + "testPreset": { "visibility": "hidden" }, + "ctest": { "visibility": "hidden" } + } +} diff --git a/tests/.vscode/tasks.json b/tests/.vscode/tasks.json new file mode 100644 index 0000000..e2f23f5 --- /dev/null +++ b/tests/.vscode/tasks.json @@ -0,0 +1,31 @@ +{ + "version": "2.0.0", + "tasks": [ + { + // Colorized CMake/Build output + // https://github.com/microsoft/vscode-cmake-tools/issues/478 + "label": "Build", + "type": "cmake", + "command": "build", + "problemMatcher":"$gcc", + "runOptions": { + "instanceLimit": 1 + }, + "presentation": { + "reveal": "always", + "panel": "shared", + "showReuseMessage": true, + "clear": true + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "echo", + "type": "shell", + "command": "echo ${command:cmake.getLaunchTargetDirectory}" + } + ] +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..3442db5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.12) +include("CMakeUtils.cmake") + +project(tests VERSION 0.0.1 LANGUAGES CXX C) + +init_project( + "tests_cpp11" + "tests_cpp20" +) + +add_executable(tests_cpp11 "main.cpp") +set_target_properties(tests_cpp11 PROPERTIES CXX_STANDARD 11) +target_compile_definitions(tests_cpp11 PRIVATE _CRT_SECURE_NO_WARNINGS) + +add_executable(tests_cpp20 "main.cpp") +set_target_properties(tests_cpp20 PROPERTIES CXX_STANDARD 20) + +fetch_git("deps/string-view-lite" https://github.com/martinmoene/string-view-lite.git master) +target_include_directories(tests_cpp11 PRIVATE "deps/string-view-lite/include/nonstd") + +fetch_git("deps/tiny-utf8" https://github.com/DuffsDevice/tiny-utf8.git master) +target_include_directories(tests_cpp11 PRIVATE "deps/tiny-utf8/include/tinyutf8") +target_include_directories(tests_cpp20 PRIVATE "deps/tiny-utf8/include/tinyutf8") diff --git a/tests/CMakePresets.json b/tests/CMakePresets.json new file mode 100644 index 0000000..75e9c1d --- /dev/null +++ b/tests/CMakePresets.json @@ -0,0 +1,190 @@ +{ + "version": 3, + "buildPresets": [ + { "name": "x64-Debug-Linux", "configurePreset": "x64-Debug-Linux" }, + { "name": "x64-Release-Linux", "configurePreset": "linux-default" }, + { "name": "x64-RelNoDebInfo-Linux", "configurePreset": "x64-RelNoDebInfo-Linux" }, + { "name": "x64-Debug-Mac", "configurePreset": "x64-Debug-Mac" }, + { "name": "x64-Release-Mac", "configurePreset": "x64-Release-Mac" }, + { "name": "x64-RelNoDebInfo-Mac", "configurePreset": "x64-RelNoDebInfo-Mac" }, + { "name": "x32-Debug-Windows", "configurePreset": "x32-Debug-Windows" }, + { "name": "x32-Release-Windows", "configurePreset": "x32-Release-Windows" }, + { "name": "x32-RelNoDebInfo-Windows", "configurePreset": "x32-RelNoDebInfo-Windows" }, + { "name": "x64-Debug-Windows", "configurePreset": "x64-Debug-Windows" }, + { "name": "x64-Release-Windows", "configurePreset": "windows-default" }, + { "name": "x64-RelNoDebInfo-Windows", "configurePreset": "x64-RelNoDebInfo-Windows" }, + { "name": "arm64-Debug-Windows", "configurePreset": "arm64-Debug-Windows" }, + { "name": "arm64-Release-Windows", "configurePreset": "arm64-Release-Windows" }, + { "name": "arm64-RelNoDebInfo-Windows", "configurePreset": "arm64-RelNoDebInfo-Windows" } + ], + "configurePresets": [ + { + "name": "LinuxBase", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/.cmake/${presetName}", + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Linux" ] } + } + }, + { + "name": "MacBase", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build/.cmake/${presetName}", + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "macOS" ] } + } + }, + { + "name": "WindowsBase", + "generator": "Visual Studio 17 2022", + "binaryDir": "${sourceDir}/build/.cmake/${presetName}", + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } + } + }, + { + "inherits": "LinuxBase", + "name": "x64-Debug-Linux", + "displayName": "x64-Debug-Linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "inherits": "LinuxBase", + "name": "linux-default", + "displayName": "x64-Release-Linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_CONFIGURATION_TYPES": "RelWithDebInfo" + } + }, + { + "inherits": "LinuxBase", + "name": "x64-RelNoDebInfo-Linux", + "displayName": "x64-RelNoDebInfo-Linux", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + }, + { + "inherits": "MacBase", + "name": "x64-Debug-Mac", + "displayName": "x64-Debug-Mac", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "inherits": "MacBase", + "name": "x64-Release-Mac", + "displayName": "x64-Release-Mac", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_CONFIGURATION_TYPES": "RelWithDebInfo" + } + }, + { + "inherits": "MacBase", + "name": "x64-RelNoDebInfo-Mac", + "displayName": "x64-RelNoDebInfo-Mac", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + }, + { + "inherits": "WindowsBase", + "name": "x32-Debug-Windows", + "displayName": "x32-Debug-Windows", + "architecture": { "value": "Win32", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "inherits": "WindowsBase", + "name": "x32-Release-Windows", + "displayName": "x32-Release-Windows", + "architecture": { "value": "Win32", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_CONFIGURATION_TYPES": "RelWithDebInfo" + } + }, + { + "inherits": "WindowsBase", + "name": "x32-RelNoDebInfo-Windows", + "displayName": "x32-RelNoDebInfo-Windows", + "architecture": { "value": "Win32", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + }, + { + "inherits": "WindowsBase", + "name": "x64-Debug-Windows", + "displayName": "x64-Debug-Windows", + "architecture": { "value": "x64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "inherits": "WindowsBase", + "name": "windows-default", + "displayName": "x64-Release-Windows", + "architecture": { "value": "x64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_CONFIGURATION_TYPES": "RelWithDebInfo" + } + }, + { + "inherits": "WindowsBase", + "name": "x64-RelNoDebInfo-Windows", + "displayName": "x64-RelNoDebInfo-Windows", + "architecture": { "value": "x64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + }, + { + "inherits": "WindowsBase", + "name": "arm64-Debug-Windows", + "displayName": "arm64-Debug-Windows", + "architecture": { "value": "ARM64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_CONFIGURATION_TYPES": "Debug" + } + }, + { + "inherits": "WindowsBase", + "name": "arm64-Release-Windows", + "displayName": "arm64-Release-Windows", + "architecture": { "value": "ARM64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "CMAKE_CONFIGURATION_TYPES": "RelWithDebInfo" + } + }, + { + "inherits": "WindowsBase", + "name": "arm64-RelNoDebInfo-Windows", + "displayName": "arm64-RelNoDebInfo-Windows", + "architecture": { "value": "ARM64", "strategy": "set" }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "CMAKE_CONFIGURATION_TYPES": "Release" + } + } + ] +} diff --git a/tests/CMakeUtils.cmake b/tests/CMakeUtils.cmake new file mode 100644 index 0000000..260be2c --- /dev/null +++ b/tests/CMakeUtils.cmake @@ -0,0 +1,960 @@ +# CMake Utils +# https://github.com/yurablok/cmake-cpp-template +# +# History: +# v0.8 2024-May-01 Added flags `-fvisibility=hidden`, `MSVC_CPU_AUTO_LIMIT`. +# v0.7 2023-Dec-27 Added `breakpad_dump_and_strip`. +# v0.6 2023-May-22 Added `--filter=tree:0` and removed `--single-branch` in `fetch_git`. +# v0.5 2023-Feb-22 Added `fetch_git`. +# v0.4 2023-Feb-20 Added git commands. +# v0.3 2023-Jan-24 Added `add_option`. +# v0.2 2022-Dec-24 Added support for Windows ARM64. +# v0.1 2022-Oct-18 First release. + +# Include this file before the main `project(...)` + + +# β–ˆβ–ˆ β–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ +# +# Call it after the main `project(...)` and before any `add_subdirectory(...)` +# Example: +# init_project("client --ip=localhost" "server") +function(init_project) + if(NOT "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + message(WARNING "CMakeUtils.cmake is not in the root.") + return() + endif() + + cmake_parse_arguments(arg "" "" "" "${ARGN}") + if(NOT DEFINED arg_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "At least one target must be specified.") + endif() + + if("${BUILD_ARCH}" STREQUAL "") + if(NOT "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}" STREQUAL "") + set(CMAKE_TARGET_ARCH ${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}) + else() + set(CMAKE_TARGET_ARCH ${CMAKE_SYSTEM_PROCESSOR}) + endif() + message("CMAKE_TARGET_ARCH: ${CMAKE_TARGET_ARCH}") + if("${CMAKE_TARGET_ARCH}" MATCHES "(arm|ARM|aarch).*") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(BUILD_ARCH "arm64") + else() + set(BUILD_ARCH "arm32") + endif() + else() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(BUILD_ARCH "x64") + else() + set(BUILD_ARCH "x32") + endif() + endif() + endif() + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Release") + set(BUILD_TYPE "RelNoDebInfo") + elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") + set(BUILD_TYPE "Release") + else() + set(BUILD_TYPE "${CMAKE_BUILD_TYPE}") + endif() + + if(UNIX) + if(APPLE) + set(BUILD_PLATFORM "Mac") + else() + set(BUILD_PLATFORM "Linux") + endif() + elseif(WIN32) + set(BUILD_PLATFORM "Windows") + else() + set(BUILD_PLATFORM "Unknown") + endif() + + set(BUILD_FOLDER "${BUILD_ARCH}-${BUILD_TYPE}-${BUILD_PLATFORM}") + message("BUILD_FOLDER: ${BUILD_FOLDER}") + + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/${BUILD_FOLDER}" PARENT_SCOPE) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/${BUILD_FOLDER}" PARENT_SCOPE) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/build/${BUILD_FOLDER}" PARENT_SCOPE) + + file(MAKE_DIRECTORY "${CMAKE_SOURCE_DIR}/build/.cmake") + file(MAKE_DIRECTORY "${CMAKE_SOURCE_DIR}/workdir") + + if("${CMAKE_SYSROOT}" STREQUAL "") + #NOTE: clangd linter config + file(WRITE ".clangd" "CompileFlags:\n CompilationDatabase: build/.cmake/${BUILD_FOLDER}\n") + set(CMAKE_EXPORT_COMPILE_COMMANDS ON PARENT_SCOPE) + endif() + + cmake_policy(SET CMP0069 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0069 NEW PARENT_SCOPE) + if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + #NOTE: Link-Time Global Optimization + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + endif() + + if(MSVC) + if(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + message("Compiler: MSVC v${CMAKE_CXX_COMPILER_VERSION}") + if(MSVC_CPU_AUTO_LIMIT) + cmake_host_system_information(RESULT totalCPU QUERY NUMBER_OF_LOGICAL_CORES) + message("NUMBER_OF_LOGICAL_CORES=${totalCPU}") + cmake_host_system_information(RESULT totalRAM_MiB QUERY AVAILABLE_PHYSICAL_MEMORY) + message("AVAILABLE_PHYSICAL_MEMORY=${totalRAM_MiB}") + math(EXPR maxCPU "${totalRAM_MiB} / 4096" OUTPUT_FORMAT DECIMAL) + message("maxCPU=${maxCPU}") + if(${maxCPU} GREATER ${totalCPU}) + set(maxCPU ${totalCPU}) + endif() + endif() + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + add_compile_options( + /ZI # Debug Information with Edit and Continue + ) + endif() + add_compile_options( + /utf-8 # Set source and execution character sets to UTF-8 + /sdl # Enable Additional Security Checks + "/MP ${maxCPU}" # Build with Multiple Processes + /permissive- # Standards conformance + ) + else() + message("Compiler: Clang v${CMAKE_CXX_COMPILER_VERSION}") + add_compile_options(-fcolor-diagnostics) + endif() + + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + add_compile_options( + /JMC # Just My Code Debugging + ) + add_link_options( + /INCREMENTAL # For Edit and Continue + ) + endif() + + #set(CMAKE_CXX_FLAGS_RELEASE = "/MD /O2 /Ob2 /DNDEBUG") + + # The /Zi option produces a separate PDB file that contains all the symbolic + # debugging information for use with the debugger. The debugging information + # isn't included in the object files or executable, which makes them much smaller. + set(CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /Zi /O2 /Ob2 /DNDEBUG" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /Zi /O2 /Ob2 /DNDEBUG" PARENT_SCOPE) + + #NOTE: When changing the Qt5_DIR, you may need to manually delete CMakeCache.txt + __find_msvc_qt5("C;D;E" "5.15.2") + __write_msvs_launch_vs_json("${arg_UNPARSED_ARGUMENTS}") + #__crutch_for_msvs_bug_with_merges() + + elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "(GNU|Clang)") + # -O3 -g0 3.4 MB default Release + # -O3 -g1 9.5 MB + # -O2 -g1 9.3 MB + # -O2 -g2 41.0 MB default RelWithDebInfo + # -O0 -g2 35.0 MB default Debug + # Level 1 produces minimal information, enough for making backtraces in parts + # of the program that you don’t plan to debug. This includes descriptions of + # functions and external variables, and line number tables, but no information + # about local variables. + set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -g1 -DNDEBUG" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g1 -DNDEBUG" PARENT_SCOPE) + + add_compile_options(-fvisibility=hidden) + + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + message("Compiler: GCC v${CMAKE_CXX_COMPILER_VERSION}") + add_compile_options(-fdiagnostics-color=always) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + add_link_options(-fuse-ld=gold) + elseif(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.1) + #add_link_options(-fuse-ld=mold) + if(${CMAKE_INTERPROCEDURAL_OPTIMIZATION}) + add_compile_options(-flto=auto) + endif() + endif() + elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + message("Compiler: Clang v${CMAKE_CXX_COMPILER_VERSION}") + add_compile_options(-fcolor-diagnostics) + endif() + + __find_gcc_qt5("5.15.2") + + else() + message(FATAL_ERROR "Unknown compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() + + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25) + set(CMAKE_CXX_STANDARD 26 PARENT_SCOPE) + elseif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20) + set(CMAKE_CXX_STANDARD 23 PARENT_SCOPE) + else() + set(CMAKE_CXX_STANDARD 20 PARENT_SCOPE) + endif() + set(CMAKE_CXX_STANDARD_REQUIRED OFF PARENT_SCOPE) + set(CMAKE_CXX_EXTENSIONS OFF PARENT_SCOPE) + + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.21) + set(CMAKE_C_STANDARD 23 PARENT_SCOPE) + else() + set(CMAKE_C_STANDARD 11 PARENT_SCOPE) + endif() + set(CMAKE_C_STANDARD_REQUIRED OFF PARENT_SCOPE) + set(CMAKE_C_EXTENSIONS OFF PARENT_SCOPE) + set(CMAKE_INCLUDE_CURRENT_DIR ON PARENT_SCOPE) + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(Qt5Path "${Qt5x64Path}" PARENT_SCOPE) + else() + set(Qt5Path "${Qt5x32Path}" PARENT_SCOPE) + endif() + if(Qt5_DIR) + set(Qt5_DIR "${Qt5_DIR}" PARENT_SCOPE) + endif() + + set(BUILD_ARCH "${BUILD_ARCH}" PARENT_SCOPE) + set(BUILD_TYPE "${BUILD_TYPE}" PARENT_SCOPE) + set(BUILD_PLATFORM "${BUILD_PLATFORM}" PARENT_SCOPE) + set(BUILD_FOLDER "${BUILD_FOLDER}" PARENT_SCOPE) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${CMAKE_INTERPROCEDURAL_OPTIMIZATION} PARENT_SCOPE) +endfunction(init_project) + + +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆ +# +# @param [0] Type: BOOL | ENUM | STRING | DIR | FILE +# @param [1] Option's name. Prefix "OPTION_" will be added. +# @param [2] (optional) Default variant +# @param [n] (optional) Other variants +# @param TARGETS Targets +# @param COMMENT (optional) Comment +function(add_option) + set(argIdx -1) + set(optionDefault "") + set(optionVariants "") + set(isTargets FALSE) + set(optionTargets "") + set(isComment FALSE) + set(optionComment "") + foreach(arg ${ARGN}) + math(EXPR argIdx "${argIdx}+1") + if(${argIdx} EQUAL 0) + set(optionType "${arg}") + + elseif(${argIdx} EQUAL 1) + set(optionName "OPTION_${arg}") + + elseif("${arg}" STREQUAL "TARGETS") + set(isTargets TRUE) + set(isComment FALSE) + + elseif("${arg}" STREQUAL "COMMENT") + set(isTargets FALSE) + set(isComment TRUE) + + elseif(${isTargets}) + list(APPEND optionTargets "${arg}") + + elseif(${isComment}) + list(APPEND optionComment "${arg}") + + else() + if(${argIdx} EQUAL 2) + set(optionDefault "${arg}") + endif() + list(APPEND optionVariants "${arg}") + endif() + endforeach() + string(REPLACE ";" "\n " optionComment "${optionComment}") + + if("${optionName}" STREQUAL "") + message(FATAL_ERROR "add_option: must be specified.") + + elseif("${optionType}" STREQUAL "") + message(FATAL_ERROR "add_option: must be specified.") + + elseif("${optionTargets}" STREQUAL "") + message(FATAL_ERROR "add_option: must be specified.") + + elseif("${optionType}" STREQUAL "BOOL") + if(NOT "${optionDefault}" MATCHES "^(ON|OFF|TRUE|FALSE|YES|NO)$") + message(FATAL_ERROR "add_option: BOOL: must be one of (ON | OFF | TRUE | FALSE | YES | NO).") + endif() + list(LENGTH optionVariants size) + if(NOT ${size} EQUAL 1) + message(FATAL_ERROR "add_option: BOOL: too many defaults.") + endif() + if("${optionComment}" STREQUAL "") + set(optionComment " Boolean option") + endif() + + if(NOT "${${optionName}}" STREQUAL "") + set(optionDefault ${${optionName}}) + endif() + set(${optionName} ${optionDefault} CACHE BOOL "${optionComment}" FORCE) + + foreach(optionTarget ${optionTargets}) + if(${optionDefault}) + target_compile_definitions(${optionTarget} PRIVATE ${optionName}=1) + else() + target_compile_definitions(${optionTarget} PRIVATE ${optionName}=0) + endif() + endforeach() + + elseif("${optionType}" STREQUAL "ENUM") + if("${optionVariants}" MATCHES ".* .*") + message(FATAL_ERROR "add_option: ENUM: variants must not contain spaces in their names.") + endif() + list(LENGTH optionVariants size) + if(${size} LESS 2) + message(FATAL_ERROR "add_option: ENUM: too few variants.") + endif() + if(NOT "${optionComment}" STREQUAL "") + string(APPEND optionComment "\n") + endif() + string(APPEND optionComment " Enum variants: ${optionVariants}") + + if(NOT "${${optionName}}" STREQUAL "") + set(optionDefault ${${optionName}}) + list(FIND optionVariants ${optionDefault} result) + if(${result} LESS 0) + message(FATAL_ERROR "add_option: ENUM: unknown variant ${optionDefault}.") + endif() + endif() + set(${optionName} ${optionDefault} CACHE STRING "${optionComment}" FORCE) + set_property(CACHE ${optionName} PROPERTY STRINGS ${optionVariants}) + + foreach(optionTarget ${optionTargets}) + target_compile_definitions(${optionTarget} PRIVATE ${optionName}="${optionDefault}") + target_compile_definitions(${optionTarget} PRIVATE ${optionName}_${optionDefault}=1) + endforeach() + + elseif("${optionType}" STREQUAL "STRING") + list(LENGTH optionVariants size) + if(${size} GREATER 1) + message(FATAL_ERROR "add_option: STRING: too many defaults.") + endif() + if("${optionComment}" STREQUAL "") + set(optionComment " String option") + endif() + + if(DEFINED ${optionName}) + set(optionDefault ${${optionName}}) + endif() + set(${optionName} ${optionDefault} CACHE STRING "${optionComment}" FORCE) + + foreach(optionTarget ${optionTargets}) + target_compile_definitions(${optionTarget} PRIVATE ${optionName}="${optionDefault}") + endforeach() + + elseif("${optionType}" STREQUAL "DIR") + list(LENGTH optionVariants size) + if(${size} GREATER 1) + message(FATAL_ERROR "add_option: DIR: too many defaults.") + endif() + if("${optionComment}" STREQUAL "") + set(optionComment " Directory option") + endif() + + if(DEFINED ${optionName}) + set(optionDefault ${${optionName}}) + endif() + set(${optionName} ${optionDefault} CACHE PATH "${optionComment}" FORCE) + + foreach(optionTarget ${optionTargets}) + target_compile_definitions(${optionTarget} PRIVATE ${optionName}="${optionDefault}") + endforeach() + + elseif("${optionType}" STREQUAL "FILE") + list(LENGTH optionVariants size) + if(${size} GREATER 1) + message(FATAL_ERROR "add_option: FILE: too many defaults.") + endif() + if("${optionComment}" STREQUAL "") + set(optionComment " File option") + endif() + + if(DEFINED ${optionName}) + set(optionDefault ${${optionName}}) + endif() + set(${optionName} ${optionDefault} CACHE FILEPATH "${optionComment}" FORCE) + + foreach(optionTarget ${optionTargets}) + target_compile_definitions(${optionTarget} PRIVATE ${optionName}="${optionDefault}") + endforeach() + + else() + message(FATAL_ERROR "add_option: must be one of (BOOL | ENUM | STRING | DIR | FILE).") + endif() + +endfunction(add_option) + + +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ + +# @param directory Target directory to download sources. +# @param address Git-compatible address of a repository. +# @param tag Desired branch | tag | hash. +function(fetch_git directory address tag) + get_filename_component(absolutePath ${directory} ABSOLUTE) + file(RELATIVE_PATH relativePath ${CMAKE_SOURCE_DIR} ${absolutePath}) + message("fetch_git: checking \"${relativePath}\"...") + + if(NOT EXISTS "${absolutePath}/.git/HEAD") + message("fetch_git: downloading \"${relativePath}\"...") + + get_filename_component(absoluteParentPath "${absolutePath}/../" ABSOLUTE) + file(MAKE_DIRECTORY ${absoluteParentPath}) + + string(LENGTH ${absoluteParentPath} length) + math(EXPR length "${length}+1") + string(SUBSTRING ${absolutePath} ${length} -1 folder) + + execute_process( + WORKING_DIRECTORY ${absoluteParentPath} + COMMAND git clone --branch ${tag} --filter=tree:0 --recurse-submodules ${address} ${folder} + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + # Normal + branch | tag + # output= + # error=Cloning into 'repo123'... + # result=0 + # Normal + hash + # output= + # error=Cloning into 'repo123'... + # warning: Could not find remote branch 1234567 to clone. + # fatal: Remote branch 1234567 not found in upstream origin + # result=128 + # Error 128 (not empty) + # output= + # error=fatal: destination path 'repo123' already exists and is not an empty directory. + # result=128 + # Error 128 (unreachable) + # output= + # error=Cloning into 'repo123'... + # fatal: unable to access 'https://....': Could not resolve host: .... + # result=128 + if(NOT ${result} EQUAL 0) + string(FIND "${error}" "not found in upstream" result) + if(${result} GREATER 0) + execute_process( + WORKING_DIRECTORY ${absoluteParentPath} + COMMAND git clone --filter=tree:0 --recurse-submodules ${address} ${folder} + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + if(NOT ${result} EQUAL 0) + message(FATAL_ERROR "fetch_git: ${error}") + endif() + + execute_process( + WORKING_DIRECTORY ${absolutePath} + COMMAND git checkout --recurse-submodules ${tag} + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + # Normal + hash + # output= + # error=Note: switching to '1234567'. + # You are in 'detached HEAD' state. + # result=0 + # Error 1 + # output= + # error=error: pathspec '1234567' did not match any file(s) known to git + # result=1 + endif() + endif() + + if(NOT ${result} EQUAL 0) + message(FATAL_ERROR "fetch_git: ${error}") + endif() + + else() + execute_process( + WORKING_DIRECTORY ${absolutePath} + COMMAND git checkout --recurse-submodules ${tag} + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + # Error 1 (no match) + # output= + # error=error: pathspec '2.5.1' did not match any file(s) known to git + # result=1 + if(NOT ${result} EQUAL 0) + message("fetch_git: fetching \"${relativePath}\"...") + file(REMOVE "${absolutePath}/.git/index.lock") + + execute_process( + WORKING_DIRECTORY ${absolutePath} + COMMAND git fetch --all --tags --recurse-submodules + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + if(NOT ${result} EQUAL 0) + message(FATAL_ERROR "fetch_git: ${error}") + endif() + + execute_process( + WORKING_DIRECTORY ${absolutePath} + COMMAND git checkout --recurse-submodules --force ${tag} + OUTPUT_VARIABLE output + ERROR_VARIABLE error + RESULT_VARIABLE result + ) + endif() + + if(NOT ${result} EQUAL 0) + message(FATAL_ERROR "fetch_git: ${error}") + endif() + + endif() + + message("fetch_git: \"${relativePath}\" is ready") +endfunction(fetch_git) + + +# @param[out] OUT_RESULT Resulting output of our command +# @param COMMAND_ Our git command +function(git_do_command OUT_RESULT COMMAND_) + set(_execute_command git ${COMMAND_} ${ARGN}) + + execute_process(COMMAND ${_execute_command} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE output + ERROR_VARIABLE error_output + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(NOT error_output STREQUAL "") + set(${OUT_RESULT} ${error_output} PARENT_SCOPE) + return() + endif() + set(${OUT_RESULT} ${output} PARENT_SCOPE) +endfunction(git_do_command) + + +# @param[out] OUT_BRANCH Resulting name of the current branch if not detached +function(git_branch OUT_BRANCH) + git_do_command(output rev-parse --abbrev-ref HEAD) + set(${OUT_BRANCH} ${output} PARENT_SCOPE) +endfunction(git_branch) + + +# @param REGEX Desired regex to match a commit by its message +# @param[out] OUT_COMMITS_COUNT Resulting number of commits from HEAD to the first matched commit +# @param[out] GIT_COMMIT_HASH Resulting hash of the first matched commit +function(git_commits_count_by_regex REGEX OUT_COMMITS_COUNT) + git_do_command(commit_hash rev-list HEAD --grep=\"${REGEX}\" -n 1) + if("${commit_hash}" STREQUAL "") + message(FATAL_ERROR "[git_commits_count_by_regex] Bad git revision") + endif() + git_do_command(output rev-list --count HEAD ^${commit_hash}) + + set(${OUT_COMMITS_COUNT} ${output} PARENT_SCOPE) + set(${GIT_COMMIT_HASH} ${commit_hash} PARENT_SCOPE) +endfunction(git_commits_count_by_regex) + +# @param TAG Desired tag to counting to +# @param[out] OUT_COMMITS_COUNT Resulting number of commits to the desired tag +function(git_commits_count_by_tag TAG OUT_COMMITS_COUNT) + git_do_command(output rev-list --count HEAD ^${TAG}) + set(${OUT_COMMITS_COUNT} ${output} PARENT_SCOPE) +endfunction(git_commits_count_by_tag) + + +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–„β–„ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–€β–€ + +# @param SOURCES Directory where lupdate will look for C++ sources +# @param TS_FILES List of generated *.ts files +# @param QM_DIR Directory for generated *.qm files +function(qt5_create_ts_and_qm) + set(options) + set(oneValueArgs SOURCES QM_DIR) + set(multiValueArgs TS_FILES) + cmake_parse_arguments(arg "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + file(MAKE_DIRECTORY "${arg_QM_DIR}") + + foreach(tsFile ${arg_TS_FILES}) + get_filename_component(tsFileName "${tsFile}" NAME_WLE) + #add_custom_command( + # TARGET ${PROJECT_NAME} PRE_BUILD + # WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + # COMMAND "${Qt5Path}/bin/lupdate" + # ARGS -locations none ${arg_SOURCES} -ts ${tsFile} + #) + #add_custom_command( + # TARGET ${PROJECT_NAME} PRE_BUILD + # DEPENDS "${tsFile}" + # WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + # COMMAND "${Qt5Path}/bin/lrelease" + # ARGS ${tsFile} -qm ${arg_QM_DIR}/${tsFileName}.qm + #) + + add_custom_command( + TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} + -DRUN=qt5_create_ts_and_qm + + -DLUPDATE=${Qt5Path}/bin/lupdate + -DLRELEASE=${Qt5Path}/bin/lrelease + -DWORKDIR=${CMAKE_CURRENT_SOURCE_DIR} + -DDIRECTORY=${arg_SOURCES} + -DTSFILE=${tsFile} + -DQMFILE=${arg_QM_DIR}/${tsFileName}.qm + + -P ${CMAKE_SOURCE_DIR}/CMakeUtils.cmake + ) + endforeach() +endfunction(qt5_create_ts_and_qm) + +function(__qt5_create_ts_and_qm_impl) + # lupdate directory -ts file.ts -locations none + execute_process( + WORKING_DIRECTORY ${WORKDIR} + COMMAND ${LUPDATE} -locations none ${DIRECTORY} -ts ${TSFILE} + OUTPUT_VARIABLE result + ) + if("${result}" STREQUAL "") + return() + endif() + + # Found 0 source text(s) (0 new and 0 already existing) + string(REGEX MATCH "([0-9]+)[ a-z()]+([0-9]+)[ a-z()]+([0-9]+)" result ${result}) + message("lupdate: ${CMAKE_MATCH_1} found, ${CMAKE_MATCH_2} new, ${CMAKE_MATCH_3} exists") + + if(EXISTS "${WORKDIR}/${QMFILE}" AND "${CMAKE_MATCH_2}" STREQUAL "0") + #NOTE: Remove *.qm files after editing *.ts files! + return() + endif() + + # lrelease file.ts -qm file.qm + execute_process( + WORKING_DIRECTORY ${WORKDIR} + COMMAND ${LRELEASE} ${TSFILE} -qm ${QMFILE} + OUTPUT_VARIABLE result + ) + if("${result}" STREQUAL "") + return() + endif() + + # Generated 0 translation(s) (0 finished and 0 unfinished) + #string(REGEX MATCH "([0-9]+)[ a-z()]+([0-9]+)[ a-z()]+([0-9]+)" result ${result}) + message("lrelease: ${QMFILE}") +endfunction(__qt5_create_ts_and_qm_impl) + + +function(__find_gcc_qt5 qtVersion) + set(qtPath "~/Qt/${qtVersion}/gcc_64") + if(EXISTS "${qtPath}") + set(Qt5x64Path "${qtPath}" PARENT_SCOPE) + else() + set(qtPath "") + endif() + + if(NOT "${qtPath}" STREQUAL "") + if(${BUILD_ARCH} STREQUAL "x64") + set(Qt5_DIR "${qtPath}/lib/cmake/Qt5" PARENT_SCOPE) + endif() + message("Qt5x64Path: ${qtPath}") + endif() +endfunction(__find_gcc_qt5) + +function(__find_msvc_qt5 drives qtVersion) + foreach(drive ${drives}) + set(qtPath "${drive}:/Qt/${qtVersion}/msvc2019") + if(EXISTS "${qtPath}") + set(Qt5x32Path "${qtPath}" PARENT_SCOPE) + break() + endif() + set(qtPath "${drive}:/Qt/${qtVersion}/msvc2017") + if(EXISTS "${qtPath}") + set(Qt5x32Path "${qtPath}" PARENT_SCOPE) + break() + endif() + set(qtPath "") + endforeach(drive) + if(NOT "${qtPath}" STREQUAL "") + if(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(Qt5_DIR "${qtPath}/lib/cmake/Qt5" PARENT_SCOPE) + endif() + message("Qt5x32Path: ${qtPath}") + endif() + + foreach(drive ${drives}) + set(qtPath "${drive}:/Qt/${qtVersion}/msvc2019_64") + if(EXISTS "${qtPath}") + set(Qt5x64Path "${qtPath}" PARENT_SCOPE) + break() + endif() + set(qtPath "${drive}:/Qt/${qtVersion}/msvc2017_64") + if(EXISTS "${qtPath}") + set(Qt5x64Path "${qtPath}" PARENT_SCOPE) + break() + endif() + set(qtPath "") + endforeach(drive) + if(NOT "${qtPath}" STREQUAL "") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(Qt5_DIR "${qtPath}/lib/cmake/Qt5" PARENT_SCOPE) + endif() + message("Qt5x64Path: ${qtPath}") + endif() +endfunction(__find_msvc_qt5) + + +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ + +# @param targetName Target name for which the symbols will be dumped +# @param BREAKPAD_DUMP_SYMS Path to the Breakpad's dump_syms executable. +# @param CMAKE_STRIP (optional) Path to the strip executable if the target platform is different. +function(breakpad_dump_and_strip targetName) + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + return() + endif() + if(NOT TARGET ${targetName}) + message(FATAL_ERROR "breakpad_dump_and_strip: wrong target ${targetName}") + endif() + if(${BUILD_PLATFORM} STREQUAL "Windows") + return() + elseif(NOT ${BUILD_PLATFORM} STREQUAL "Linux") + message(WARNING "breakpad_dump_and_strip: ${BUILD_PLATFORM} platform has not been tested") + return() + endif() + if("${BREAKPAD_DUMP_SYMS}" STREQUAL "") + message(WARNING "breakpad_dump_and_strip: BREAKPAD_DUMP_SYMS is not specified") + return() + endif() + if(NOT EXISTS "${BREAKPAD_DUMP_SYMS}") + message(FATAL_ERROR "breakpad_dump_and_strip: dump_syms is not found (path: ${BREAKPAD_DUMP_SYMS})") + endif() + + add_custom_command( + VERBATIM + TARGET ${targetName} POST_BUILD + WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + COMMAND ${CMAKE_COMMAND} + -DRUN=breakpad_dump_and_strip + + -DBREAKPAD_DUMP_SYMS=${BREAKPAD_DUMP_SYMS} + -DCMAKE_STRIP=${CMAKE_STRIP} + -DTARGET_FILE=${targetName} + + -P ${CMAKE_SOURCE_DIR}/CMakeUtils.cmake + ) +endfunction(breakpad_dump_and_strip) + +function(__breakpad_dump_and_strip) + execute_process( + COMMAND "${BREAKPAD_DUMP_SYMS}" -i ${TARGET_FILE} + OUTPUT_VARIABLE result + ) + # "MODULE Linux arm64 912C385C93DFB00C2B4D31F83BFF5BF90 TARGET_FILE" + string(REGEX MATCH "MODULE[ ]+[a-zA-Z0-9]+[ ]+[a-zA-Z0-9]+[ ]+([a-zA-Z0-9]+)[ ]+" result ${result}) + set(buildId "${CMAKE_MATCH_1}") + message("${TARGET_FILE} build id: ${buildId}") + file(MAKE_DIRECTORY "symbols/${TARGET_FILE}/${buildId}/") + + execute_process( + COMMAND "${BREAKPAD_DUMP_SYMS}" ${TARGET_FILE} + OUTPUT_VARIABLE result + ) + file(WRITE "symbols/${TARGET_FILE}/${buildId}/${TARGET_FILE}.sym" "${result}") + + cmake_minimum_required(VERSION 3.18) + file(ARCHIVE_CREATE + OUTPUT "${TARGET_FILE}.sym.zip" + PATHS "symbols/${TARGET_FILE}/${buildId}/${TARGET_FILE}.sym" + FORMAT zip + ) + file(REMOVE_RECURSE "symbols") + + if("${CMAKE_STRIP}" STREQUAL "") + execute_process( + COMMAND strip ${TARGET_FILE} + ) + else() + execute_process( + COMMAND "${CMAKE_STRIP}" ${TARGET_FILE} + ) + endif() + + message("${TARGET_FILE}.sym.zip created") +endfunction(__breakpad_dump_and_strip) + + +# β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ +# β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ + +function(__write_msvs_launch_vs_json targets) + set(cfgPath "${CMAKE_SOURCE_DIR}/.vs/launch.vs.json") + + set(json "") + set(json "${json}{\n") + set(json "${json} \"NOTE\": \"This file was generated by CMakeUtils.cmake\",\n") + set(json "${json} \"version\": \"0.2.1\",\n") + set(json "${json} \"configurations\": [\n") + + macro(add appPath qtPath targetName targetArgs) + string(REPLACE "/" "\\\\" targetPath "${CMAKE_SOURCE_DIR}/build/${appPath}/${targetName}") + set(json "${json} {\n") + set(json "${json} \"type\": \"default\",\n") + set(json "${json} \"project\": \"CMakeLists.txt\",\n") + set(json "${json} \"projectTarget\": \"${targetName}.exe (${targetPath}.exe)\",\n") + set(json "${json} \"name\": \"${targetName}\",\n") + set(json "${json} \"args\": [\n") + set(json "${json} \"${targetArgs}\"\n") + set(json "${json} ],\n") + set(json "${json} \"currentDir\": \"\${workspaceRoot}/workdir\",\n") + set(json "${json} \"env\": {\n") + set(json "${json} \"PATH\": \"\${env.PATH};${qtPath}/bin\"\n") + set(json "${json} }\n") + set(json "${json} },\n") + endmacro(add) + + foreach(target ${targets}) + string(REGEX MATCH "^([-_a-zA-Z0-9]+)[ ]*(.*)$" matched "${target}") + if(${CMAKE_MATCH_COUNT} EQUAL 0) + message(FATAL_ERROR "Wrong target format (actual='${target}', expected='name args').") + endif() + + set(targetName ${CMAKE_MATCH_1}) + set(targetArgs ${CMAKE_MATCH_2}) + string(REPLACE "\"" "\\\\\"" targetArgs "${targetArgs}") + #message("targetName=${targetName} targetArgs=[${targetArgs}]") + + add("x32-Debug-Windows/Debug" "${Qt5x32Path}" "${targetName}" "${targetArgs}") + add("x32-Release-Windows/RelWithDebInfo" "${Qt5x32Path}" "${targetName}" "${targetArgs}") + add("x32-RelNoDebInfo-Windows/Release" "${Qt5x32Path}" "${targetName}" "${targetArgs}") + add("x64-Debug-Windows/Debug" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + add("x64-Release-Windows/RelWithDebInfo" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + add("x64-RelNoDebInfo-Windows/Release" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + add("arm64-Debug-Windows/Debug" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + add("arm64-Release-Windows/RelWithDebInfo" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + add("arm64-RelNoDebInfo-Windows/Release" "${Qt5x64Path}" "${targetName}" "${targetArgs}") + endforeach() + + set(json "${json} ]\n") + set(json "${json}}\n") + + string(SHA256 jsonHash "${json}") + if("${launchVsJsonHash}" STREQUAL "${jsonHash}") + return() + endif() + set(launchVsJsonHash "${jsonHash}" CACHE INTERNAL "") + + message("Write ${cfgPath}") + file(WRITE "${cfgPath}" "reload") # Reload the config by using some JSON-error + file(WRITE "${cfgPath}" "${json}") +endfunction(__write_msvs_launch_vs_json) + + +# CPU leak by: +# "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/ServiceHub/Hosts/ServiceHub.Host.Dotnet.arm64/ServiceHub.IndexingService.exe" +# Memory leak in: +# "C:/..../myproject/.vs/myproject/FileContentIndex/merges" +# https://stackoverflow.com/questions/72237599/how-to-disable-that-new-filecontentindex-folder-and-vsidx-files-in-vs-2022 +# "C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/IDE/CommonExtensions/Microsoft/Editor/ServiceHub/Indexing.servicehub.service.json" +function(__crutch_for_msvs_bug_with_merges) + get_filename_component(absolutePatentPath "${CMAKE_SOURCE_DIR}/../" ABSOLUTE) + set(absolutePath "${CMAKE_SOURCE_DIR}") + string(LENGTH ${absolutePatentPath} length) + math(EXPR length "${length}+1") + string(SUBSTRING ${absolutePath} ${length} -1 folder) + set(mergesPath "${CMAKE_SOURCE_DIR}/.vs/${folder}/FileContentIndex/merges") + + if(EXISTS "${mergesPath}/") + file(REMOVE_RECURSE "${mergesPath}/") + file(TOUCH "${mergesPath}") + endif() +endfunction(__crutch_for_msvs_bug_with_merges) + + +function(copy_release_file_to_workdir frompath topath) + if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + return() + endif() + + if(MSVC) + set(fullfrom "${CMAKE_SOURCE_DIR}/build/${BUILD_FOLDER}/${CMAKE_BUILD_TYPE}/${frompath}") + else() + set(fullfrom "${CMAKE_SOURCE_DIR}/build/${BUILD_FOLDER}/${frompath}") + endif() + set(fullto "${CMAKE_SOURCE_DIR}/workdir/bin_${BUILD_ARCH}/${topath}") + + add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${fullfrom}" "${fullto}" + ) +endfunction(copy_release_file_to_workdir) + +macro(copy_release_app_to_workdir basename) + if(WIN32) + copy_release_file_to_workdir("${basename}.exe" "${basename}.exe") + else() + copy_release_file_to_workdir("${basename}" "${basename}") + endif() +endmacro(copy_release_app_to_workdir) + +macro(copy_release_lib_to_workdir basename) + if(WIN32) + copy_release_file_to_workdir("${basename}.dll" "${basename}.dll") + else() + copy_release_file_to_workdir("lib${basename}.so" "${basename}.so") + endif() +endmacro(copy_release_lib_to_workdir) + + +if("${RUN}" STREQUAL "") + if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") + message(FATAL_ERROR + "In-source builds not allowed. Please use a build directory.\n" + "For example: \"build/.cmake/x64-Release-Linux\"" + ) + else() + message("${CMAKE_BINARY_DIR}") + endif() + + if("${CMAKE_BUILD_TYPE}" STREQUAL "") + set(default_build_type "RelWithDebInfo") + set(CMAKE_BUILD_TYPE "RelWithDebInfo") + endif() + +elseif("${RUN}" STREQUAL "qt5_create_ts_and_qm") + __qt5_create_ts_and_qm_impl() + +elseif("${RUN}" STREQUAL "breakpad_dump_and_strip") + __breakpad_dump_and_strip() + +endif() diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..440568a --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,176 @@ +#include "../string_utils.hpp" +#include + + +int32_t main() { + const char* const null = nullptr; + std::string str = reinterpret_cast(u8"🌍"); + std::string_view strv = reinterpret_cast(u8"🌍"); + u8string u8str = u8"🌍"; + u8string_view u8strv = u8"🌍"; +# if defined(__cpp_char8_t) + std::u8string su8str = u8"🌍"; + std::u8string_view su8strv = u8"🌍"; + const char8_t* const u8null = nullptr; +# endif + + assert(u8string("").empty()); + assert(u8string(u8"").empty()); + assert(u8string(null).empty()); + assert(u8string(null, 0).empty()); + assert(u8string(str) != null); + assert(u8string(str) != "o"); + assert(u8string(str) == u8"🌍"); + assert(u8string(str) == str); + assert(u8string(strv) == strv); + assert(u8string(u8str) == u8str); + assert(u8string(u8strv) == u8strv); +# if defined(__cpp_char8_t) + assert(u8string(su8str) == su8str); + assert(u8string(su8strv) == su8strv); + assert(u8string(str) != u8null); + assert(u8string(u8null).empty()); + assert(u8string(u8null, 0).empty()); +# endif + + assert(u8string_view("").empty()); + assert(u8string_view(u8"").empty()); + assert(u8string_view(null).empty()); + assert(u8string_view(null, 0).empty()); + assert(u8string_view(str) != null); + assert(u8string_view(str) != "o"); + assert(u8string_view(str) == u8"🌍"); + assert(u8string_view(str) == str); + assert(u8string_view(strv) == strv); + assert(u8string_view(u8str) == u8str); + assert(u8string_view(u8strv) == u8strv); +# if defined(__cpp_char8_t) + assert(u8string_view(su8str) == su8str); + assert(u8string_view(su8strv) == su8strv); + assert(u8string_view(str) != u8null); + assert(u8string_view(u8null).empty()); + assert(u8string_view(u8null, 0).empty()); +# endif + + u8str = null; + u8str = "o"; + u8str = u8"🌍"; + u8str = str; + u8str = std::move(str); + u8str = strv; + u8str = std::move(strv); + u8str = u8str; + u8str = std::move(u8str); + u8str = u8strv; + u8str = std::move(u8strv); +# if defined(__cpp_char8_t) + u8str = su8str; + u8str = std::move(su8str); + u8str = su8strv; + u8str = std::move(su8strv); + u8str = u8null; +# endif + + u8strv = null; + u8strv = "o"; + u8strv = u8"🌍"; + u8strv = str; + u8strv = strv; + u8strv = u8str; + u8strv = u8strv; +# if defined(__cpp_char8_t) + u8strv = su8str; + u8strv = su8strv; + u8strv = u8null; +# endif + + u8str += null; + u8str += "o"; + u8str += u8"🌍"; + u8str += str; + u8str += strv; + u8str += u8str; + u8str += u8strv; +# if defined(__cpp_char8_t) + u8str += su8str; + u8str += su8strv; + u8str += u8null; +# endif + { + u8string buffer(32, '\0'); + u8string_view result = utils::to_string(12.34f, buffer); + assert(!result.empty()); + assert(result == "12.34"); + } + { + u8string buffer(32, '\0'); + u8string_view result = utils::to_string(0xDEADBEEF, buffer, true); + assert(!result.empty()); + assert(result == "deadbeef"); + } + { + float f32 = 0.0f; + assert(utils::from_string("12.34", f32)); + assert(f32 == 12.34f); + } + { + uint32_t u32 = 0; + assert(utils::from_string("DEADBEEF", u32, true)); + assert(u32 == 0xDEADBEEF); + assert(utils::from_string("deadbeef", u32, true)); + assert(u32 == 0xDEADBEEF); + } + { + u8string_view result = utils::trimm("\n 12.34 \t"); + assert(result == "12.34"); + } + { + utils::split(u8"|12||🌍|34|5\\|6|", "|", [](u8string_view part, uint32_t idx) { + switch (idx) { + case 0: assert(part == "12"); break; + case 1: assert(part == u8"🌍"); break; + case 2: assert(part == "34"); break; + case 3: assert(part == "5\\|6"); break; + default: assert(false); break; + } + }); + } + { + constexpr u8string_view str = "user@email.com"; + size_t offset = 0; + u8string_view user = utils::substr(str, offset, "@"); + assert(user == "user"); + u8string_view email = utils::substr(str, offset, "."); + assert(email == "email"); + u8string_view com = utils::substr(str, offset, "."); + assert(com == "com"); + } + { + u8string u8str; + u16string u16str; + u32string u32str; + + //u8str = u8"🌍\0"; + //assert(u8str.size() == 5); + //assert(u8str.length() == 2); + + u8str = u8"🌍"; + assert(u8str.size() == 4); + assert(u8str.length() == 1); + assert(u8str.sso_active()); + + u8str = u8"δ½ ε₯½ Hola Hello OlΓ‘ ΠŸΡ€ΠΈΠ²Π΅Ρ‚ こんにけは μ•ˆλ…•ν•˜μ„Έμš” Bonjour Hallo Ciao"; + assert(u8str.size() == 86); + assert(u8str.length() == 55); + assert(!u8str.sso_active()); + + u16str = u8str.toUtf16(); + assert(u16str.toUtf8() == u8str); + assert(u16str.toUtf32().toUtf8() == u8str); + + u32str = u8str.toUtf32(); + assert(u32str.toUtf8() == u8str); + assert(u32str.toUtf16().toUtf8() == u8str); + } + return 0; +} diff --git a/tests/utf8.natvis b/tests/utf8.natvis new file mode 100644 index 0000000..8f19ed9 --- /dev/null +++ b/tests/utf8.natvis @@ -0,0 +1,260 @@ + + + + + + + + + + + {t_sso.data,s8} + {t_non_sso.data,s8} + t_sso.data,s8 + t_non_sso.data,s8 + + + _size_b_() + length() + _capacity_b_() + _small_() + + + + + + + + + + + width = 1 + ch = _data_()[offset] + + + + width = 2 + ch = ((_data_()[offset] & 0x1F) << 6) + | (_data_()[offset + 1] & 0x3F) + + + + + width = 3 + ch = ((_data_()[offset] & 0x0F) << 12) + | ((_data_()[offset + 1] & 0x3F) << 6) + | (_data_()[offset + 2] & 0x3F) + + + + + width = 4 + ch = ((_data_()[offset] & 0x07) << 18) + | ((_data_()[offset + 1] & 0x3F) << 12) + | ((_data_()[offset + 2] & 0x3F) << 6) + | (_data_()[offset + 3] & 0x3F) + + + + + width = 5 + ch = ((_data_()[offset] & 0x03) << 24) + | ((_data_()[offset + 1] & 0x3F) << 18) + | ((_data_()[offset + 2] & 0x3F) << 12) + | ((_data_()[offset + 3] & 0x3F) << 6) + | (_data_()[offset + 4] & 0x3F) + + + + "[invalid_codepoint]" + + + + "[unexpected_end]" + + + + + ch,X + + + + ch,c + + offset += width + index += 1 + + + + + + + + + + + {_data_(),s8} + _data_(),s8 + + + _size_b_() + + + + + + + + + + + width = 1 + ch = _data_()[offset] + + + + width = 2 + + ch = ((_data_()[offset] & 0x1F) << 6) + | (_data_()[offset + 1] & 0x3F) + + + + + width = 3 + + ch = ((_data_()[offset] & 0x0F) << 12) + | ((_data_()[offset + 1] & 0x3F) << 6) + | (_data_()[offset + 2] & 0x3F) + + + + + width = 4 + + ch = ((_data_()[offset] & 0x07) << 18) + | ((_data_()[offset + 1] & 0x3F) << 12) + | ((_data_()[offset + 2] & 0x3F) << 6) + | (_data_()[offset + 3] & 0x3F) + + + + + width = 5 + + ch = ((_data_()[offset] & 0x03) << 24) + | ((_data_()[offset + 1] & 0x3F) << 18) + | ((_data_()[offset + 2] & 0x3F) << 12) + | ((_data_()[offset + 3] & 0x3F) << 6) + | (_data_()[offset + 4] & 0x3F) + + + + "[invalid_codepoint]" + + + + "[unexpected_end]" + + + + + ch,X + + + + ch,c + + offset += width + index += 1 + + + + + + + + + {_data_(),s8} + _data_(),s8 + + + _size_b_() + + + + + + + + + + + width = 1 + ch = _data_()[offset] + + + + width = 2 + + ch = ((_data_()[offset] & 0x1F) << 6) + | (_data_()[offset + 1] & 0x3F) + + + + + width = 3 + + ch = ((_data_()[offset] & 0x0F) << 12) + | ((_data_()[offset + 1] & 0x3F) << 6) + | (_data_()[offset + 2] & 0x3F) + + + + + width = 4 + + ch = ((_data_()[offset] & 0x07) << 18) + | ((_data_()[offset + 1] & 0x3F) << 12) + | ((_data_()[offset + 2] & 0x3F) << 6) + | (_data_()[offset + 3] & 0x3F) + + + + + width = 5 + + ch = ((_data_()[offset] & 0x03) << 24) + | ((_data_()[offset + 1] & 0x3F) << 18) + | ((_data_()[offset + 2] & 0x3F) << 12) + | ((_data_()[offset + 3] & 0x3F) << 6) + | (_data_()[offset + 4] & 0x3F) + + + + "[invalid_codepoint]" + + + + "[unexpected_end]" + + + + + ch,X + + + + ch,c + + offset += width + index += 1 + + + + +