diff --git a/rx/CMakeLists.txt b/rx/CMakeLists.txt index aeb489352..cf3f48323 100644 --- a/rx/CMakeLists.txt +++ b/rx/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(${PROJECT_NAME} OBJECT src/SharedAtomic.cpp src/SharedCV.cpp src/SharedMutex.cpp + src/StrUtil.cpp src/Version.cpp ) diff --git a/rx/include/rx/StrUtil.hpp b/rx/include/rx/StrUtil.hpp new file mode 100644 index 000000000..dcfc3b1b9 --- /dev/null +++ b/rx/include/rx/StrUtil.hpp @@ -0,0 +1,369 @@ +#pragma once + +#include "FunctionRef.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rx { +std::wstring toWchar(std::string_view src); +std::string toUtf8(std::wstring_view src); +std::string toUtf8(std::u16string_view src); +std::u16string toUtf16(std::string_view src); + +// Copy null-terminated string from a std::string or a char array to a char +// array with truncation +template void strcpyTrunc(D &&dst, const T &src) { + const std::size_t count = std::size(src) >= std::size(dst) + ? std::max(std::size(dst), 1) - 1 + : std::size(src); + std::memcpy(std::data(dst), std::data(src), count); + std::memset(std::data(dst) + count, 0, std::size(dst) - count); +} + +std::string replaceAll(std::string_view src, std::string_view from, + std::string_view to, std::size_t count = -1); + +template +std::string replaceAll( + std::string src, + const std::pair (&list)[list_size]) { + for (std::size_t pos = 0; pos < src.length(); ++pos) { + for (std::size_t i = 0; i < list_size; ++i) { + const std::size_t comp_length = list[i].first.length(); + + if (src.length() - pos < comp_length) { + continue; + } + + if (std::string_view(src).substr(pos, comp_length) == list[i].first) { + src.replace(pos, comp_length, list[i].second); + pos += list[i].second.length() - 1; + break; + } + } + } + + return src; +} + +template +std::string +replaceAll(std::string src, + const std::pair> ( + &list)[list_size]) { + for (std::size_t pos = 0; pos < src.length(); ++pos) { + for (std::size_t i = 0; i < list_size; ++i) { + const std::size_t comp_length = list[i].first.length(); + + if (src.length() - pos < comp_length) { + continue; + } + + if (std::string_view(src).substr(pos, comp_length) == list[i].first) { + auto replacement = list[i].second(); + src.replace(pos, comp_length, replacement); + pos += replacement.length() - 1; + break; + } + } + } + + return src; +} + +inline std::string replaceAll( + std::string src, + const std::vector> &list) { + for (std::size_t pos = 0; pos < src.length(); ++pos) { + for (const auto &i : list) { + const std::size_t comp_length = i.first.length(); + + if (src.length() - pos < comp_length) { + continue; + } + + if (std::string_view(src).substr(pos, comp_length) == i.first) { + src.replace(pos, comp_length, i.second); + pos += i.second.length() - 1; + break; + } + } + } + + return src; +} + +constexpr std::pair +splitPair(std::string_view source, + std::initializer_list separators) { + std::size_t pos = std::string_view::npos; + std::size_t sepLen = 0; + + for (auto separator : separators) { + if (std::size_t sepPos = source.find(separator); sepPos < pos) { + pos = sepPos; + sepLen = separator.length(); + } + } + + if (!sepLen) { + return {source, {}}; + } + + return {source.substr(0, pos), source.substr(pos + sepLen)}; +} + +template + requires requires(T &container, std::string_view string) { + container.emplace_back(string); + } +constexpr T splitTo(std::string_view source, + std::initializer_list separators, + bool skipEmpty = true) { + T result; + + while (!source.empty()) { + auto [piece, rest] = splitPair(source, separators); + + source = rest; + + if (!piece.empty() || !skipEmpty) { + result.emplace_back(piece); + } + } + + if (result.empty() && !skipEmpty) { + result.emplace_back(); + } + + return result; +} + +struct Splitter { + struct EndIterator {}; + struct iterator { + constexpr iterator(std::string_view string, + std::initializer_list separators, + bool skipEmpty) + : mString(string), mSeparators(separators), mSkipEmpty(skipEmpty) { + advance(); + } + + constexpr iterator &operator++() { advance(); return *this; } + constexpr std::string_view operator*() const { return mPiece; } + + constexpr bool operator==(const EndIterator &) const { + return mPiece.empty() && mString.empty(); + } + + private: + constexpr void advance() { + auto [piece, rest] = splitPair(mString, mSeparators); + mString = rest; + mPiece = piece; + + while (mSkipEmpty && mPiece.empty() && !mString.empty()) [[unlikely]] { + auto [piece, rest] = splitPair(mString, mSeparators); + mString = rest; + mPiece = piece; + } + } + std::string_view mString; + std::string_view mPiece; + std::initializer_list mSeparators; + bool mSkipEmpty; + }; + + constexpr Splitter(std::string_view string, + std::initializer_list separators, + bool skipEmpty) + : mString(string), mSeparators(separators), mSkipEmpty(skipEmpty) {} + + constexpr iterator begin() const { + return {mString, mSeparators, mSkipEmpty}; + } + constexpr EndIterator end() const { return {}; } + +private: + std::string_view mString; + std::initializer_list mSeparators; + bool mSkipEmpty; +}; + +constexpr Splitter +split(std::string_view string, + std::initializer_list separators = {" ", "\t", "\v", + "\n", "\r"}, + bool skipEmpty = true) { + return {string, separators, skipEmpty}; +} + +constexpr std::string_view trimPrefix(std::string_view source, + std::string_view values = " \t\v\n\r") { + const auto begin = source.find_first_not_of(values); + + if (begin == source.npos) + return {}; + + return source.substr(begin); +} + +constexpr std::string_view trimSuffix(std::string_view source, + std::string_view values = " \t\v\n\r") { + const std::size_t index = source.find_last_not_of(values); + source.remove_suffix(source.size() - (index + 1)); + return source; +} + +constexpr std::string_view trim(std::string_view source, + std::string_view values = " \t\v\n\r") { + return trimSuffix(trimPrefix(source, values), values); +} + +template +constexpr std::string join(const T &source, std::string_view separator) + requires requires { + { source.empty() } -> std::convertible_to; + ++source.begin(); + --source.end(); + std::string{*source.begin()}; + std::string{source.back()}; + } +{ + if (source.empty()) { + return {}; + } + + std::string result; + + auto it = source.begin(); + auto end = source.end(); + for (--end; it != end; ++it) { + if constexpr (requires { result += *it; }) { + result += *it; + } else { + result += std::string{*it}; + } + result += separator; + } + + if constexpr (requires { result += source.back(); }) { + result += source.back(); + } else { + result += std::string{source.back()}; + } + return result; +} + +template +constexpr std::string join(std::span sources, std::string_view separator) + requires requires { join(sources.front(), separator); } +{ + if (sources.empty()) { + return {}; + } + + std::string result; + bool first = true; + + for (const auto &v : sources) { + if (first) { + result = join(v, separator); + first = false; + } else { + result += separator; + result += join(v, separator); + } + } + + return result; +} + +std::string toUpper(std::string_view string); +std::string toLower(std::string_view string); +std::string truncateString(std::string_view src, std::size_t length); +bool matchString(std::string_view source, std::string_view mask); + +struct StringHash { + using hash_type = std::hash; + using is_transparent = void; + + std::size_t operator()(const char *str) const { return hash_type{}(str); } + std::size_t operator()(std::string_view str) const { + return hash_type{}(str); + } + std::size_t operator()(std::string const &str) const { + return hash_type{}(str); + } +}; + +struct StringLess { + using is_transparent = void; + + template + constexpr bool operator()( + std::basic_string_view lhs, + std::type_identity_t> rhs) const { + if (lhs.size() < rhs.size()) { + return true; + } + + if (lhs.size() > rhs.size()) { + return false; + } + + return Traits::compare(lhs.data(), rhs.data(), lhs.size()) < 0; + } + + constexpr bool operator()(std::string_view lhs, std::string_view rhs) const { + if (lhs.size() < rhs.size()) { + return true; + } + + if (lhs.size() > rhs.size()) { + return false; + } + + return std::char_traits::compare(lhs.data(), rhs.data(), lhs.size()) < + 0; + } +}; + +struct StringGreater { + using is_transparent = void; + + template + constexpr bool operator()( + std::basic_string_view lhs, + std::type_identity_t> rhs) const { + if (lhs.size() > rhs.size()) { + return true; + } + + if (lhs.size() < rhs.size()) { + return false; + } + + return Traits::compare(lhs.data(), rhs.data(), lhs.size()) > 0; + } + + constexpr bool operator()(std::string_view lhs, std::string_view rhs) const { + if (lhs.size() > rhs.size()) { + return true; + } + + if (lhs.size() < rhs.size()) { + return false; + } + + return std::char_traits::compare(lhs.data(), rhs.data(), lhs.size()) > + 0; + } +}; +} // namespace rx diff --git a/rx/src/StrUtil.cpp b/rx/src/StrUtil.cpp new file mode 100644 index 000000000..d601934ee --- /dev/null +++ b/rx/src/StrUtil.cpp @@ -0,0 +1,147 @@ +#include "StrUtil.hpp" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +std::wstring rx::toWchar(std::string_view src) { +#ifdef _WIN32 + std::wstring wchar_string; + const int size = ::narrow(src.size()); + const auto tmp_size = + MultiByteToWideChar(CP_UTF8, 0, src.data(), size, nullptr, 0); + wchar_string.resize(tmp_size); + MultiByteToWideChar(CP_UTF8, 0, src.data(), size, wchar_string.data(), + tmp_size); + return wchar_string; +#else + std::wstring_convert, wchar_t> converter{}; + return converter.from_bytes(src.data()); +#endif +} + +std::string rx::toUtf8(std::wstring_view src) { +#ifdef _WIN32 + std::string utf8_string; + const int size = ::narrow(src.size()); + const auto tmp_size = WideCharToMultiByte(CP_UTF8, 0, src.data(), size, + nullptr, 0, nullptr, nullptr); + utf8_string.resize(tmp_size); + WideCharToMultiByte(CP_UTF8, 0, src.data(), size, utf8_string.data(), + tmp_size, nullptr, nullptr); + return utf8_string; +#else + std::wstring_convert, wchar_t> converter{}; + return converter.to_bytes(src.data()); +#endif +} + +std::string rx::toUtf8(std::u16string_view src) { + std::wstring_convert, char16_t> converter{}; + return converter.to_bytes(src.data()); +} + +std::u16string rx::toUtf16(std::string_view src) { + std::wstring_convert, char16_t> converter{}; + return converter.from_bytes(src.data()); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +std::string rx::replaceAll(std::string_view src, std::string_view from, + std::string_view to, std::size_t count) { + std::string target; + target.reserve(src.size() + to.size()); + + for (std::size_t i = 0, replaced = 0; i < src.size();) { + const std::size_t pos = src.find(from, i); + + if (pos == std::string_view::npos || replaced++ >= count) { + // No match or too many encountered, append the rest of the string as is + target.append(src.substr(i)); + break; + } + + // Append source until the matched string position + target.append(src.substr(i, pos - i)); + + // Replace string + target.append(to); + i = pos + from.size(); + } + + return target; +} + +std::string rx::toUpper(std::string_view string) { + std::string result; + result.resize(string.size()); + std::ranges::transform(string, result.begin(), ::toupper); + return result; +} + +std::string rx::toLower(std::string_view string) { + std::string result; + result.resize(string.size()); + std::ranges::transform(string, result.begin(), ::tolower); + return result; +} + +std::string rx::truncateString(std::string_view src, std::size_t length) { + return {src.begin(), src.begin() + std::min(src.size(), length)}; +} + +bool rx::matchString(std::string_view source, std::string_view mask) { + std::size_t source_position = 0, mask_position = 0; + + for (; source_position < source.size() && mask_position < mask.size(); + ++mask_position, ++source_position) { + switch (mask[mask_position]) { + case '?': + break; + + case '*': + for (std::size_t test_source_position = source_position; + test_source_position < source.size(); ++test_source_position) { + if (matchString(source.substr(test_source_position), + mask.substr(mask_position + 1))) { + return true; + } + } + return false; + + default: + if (source[source_position] != mask[mask_position]) { + return false; + } + + break; + } + } + + if (source_position != source.size()) + return false; + + if (mask_position != mask.size()) + return false; + + return true; +}