#pragma once #if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) #define MIJIN_UTIL_STRING_HPP_INCLUDED 1 #include #include #include #include #include #include #include #include #include #include #include "./iterators.hpp" namespace mijin { // // public defines // // // public constants // // // public traits // template using char_type_t = decltype(std::string_view(std::declval()))::value_type; // // public types // struct SplitOptions { std::size_t limitParts = std::numeric_limits::max(); bool ignoreEmpty = true; }; // // public functions // template [[nodiscard]] std::string join(const TRange& elements, const char* const delimiter) { std::ostringstream oss; auto first = std::begin(elements); auto last = std::end(elements); if (first != last) { std::copy(first, std::prev(last), std::ostream_iterator(oss, delimiter)); first = std::prev(last); } if (first != last) { oss << *first; } return oss.str(); } namespace detail { template std::vector> splitImpl(std::basic_string_view stringView, std::basic_string_view separator, const SplitOptions& options) { using sv_t = std::basic_string_view; MIJIN_ASSERT(options.limitParts > 0, "Cannot split to zero parts."); MIJIN_ASSERT(!separator.empty(), "Separator cannot be empty."); if (separator.empty()) { return {}; } std::vector result; if (options.limitParts <= 1) { result.push_back(stringView); return result; } auto start = stringView.begin(); auto pos = start; const auto end = stringView.end(); auto seperatorFound = [&]() { return pos <= (end - separator.size()) && sv_t(pos, pos + separator.size()) == separator; }; while (pos != end) { if (seperatorFound()) { if (!options.ignoreEmpty || pos != start) { result.emplace_back(start, pos); } start = pos = (pos + separator.size()); if (result.size() == options.limitParts - 1) { if (options.ignoreEmpty) { while (seperatorFound()) { pos += separator.size(); } } result.emplace_back(pos, end); return result; } if (!options.ignoreEmpty && pos == end) { // skipped a separator at the very end, add an empty entry result.emplace_back(""); return result; } } else { ++pos; } } if (start != end) { result.emplace_back(start, end); } return result; } template bool equalsIgnoreCaseImpl(std::basic_string_view stringA, std::basic_string_view stringB) noexcept { if (stringA.size() != stringB.size()) { return false; } for (const auto [charA, charB] : zip(stringA, stringB)) { if (std::tolower(charA) != std::tolower(charB)) { return false; } } return true; } template std::basic_string_view trimPrefixImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size())); return stringView; } template std::basic_string_view trimSuffixImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size())); return stringView; } template std::basic_string_view trimImpl(std::basic_string_view stringView, std::basic_string_view charsToTrim) { return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim); } template static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')}; template static const std::basic_string_view> DEFAULT_TRIM_CHARS = {DEFAULT_TRIM_CHARS_DATA.begin(), DEFAULT_TRIM_CHARS_DATA.end()}; } template [[nodiscard]] auto split(TLeft&& stringView, TRight&& separator, const SplitOptions& options = {}) { return detail::splitImpl(std::basic_string_view(std::forward(stringView)), std::basic_string_view(std::forward(separator)), options); } template [[nodiscard]] auto trimPrefix(TString&& string, TChars&& chars) { return detail::trimPrefixImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trimPrefix(TString&& string) { return trimPrefix(string, detail::DEFAULT_TRIM_CHARS>); } template [[nodiscard]] auto trimSuffix(TString&& string, TChars&& chars) { return detail::trimSuffixImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trimSuffix(TString&& string) { return trimSuffix(string, detail::DEFAULT_TRIM_CHARS>); } template [[nodiscard]] auto trim(TString&& string, TChars&& chars) { return detail::trimImpl(std::string_view(std::forward(string)), std::string_view(std::forward(chars))); } template [[nodiscard]] auto trim(TString&& string) { return trim(string, detail::DEFAULT_TRIM_CHARS>); } template [[nodiscard]] bool equalsIgnoreCase(TLeft&& left, TRight&& right) noexcept { return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right)); } template void makeLower(std::basic_string& string) { std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr) { return std::tolower(chr, locale); }); } template void makeUpper(std::basic_string& string) { std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr) { return std::toupper(chr, locale); }); } template [[nodiscard]] auto toLower(TArgs&&... args) { std::basic_string string(std::forward(args)...); makeLower(string); return string; } template [[nodiscard]] auto toUpper(TArgs&&... args) { std::basic_string string(std::forward(args)...); makeUpper(string); return string; } template [[nodiscard]] bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) noexcept { const char* start = &*stringView.begin(); const char* end = start + stringView.size(); const std::from_chars_result res = std::from_chars(start, end, outNumber, base); return res.ec == std::errc{} && res.ptr == end; } namespace pipe { struct Join { const char* delimiter; explicit Join(const char* delimiter_) noexcept : delimiter(delimiter_) {} }; template auto operator|(TIterable&& iterable, const Join& joiner) { return join(std::forward(iterable), joiner.delimiter); } } } // namespace mijin #endif // !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)