#pragma once #if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED) #define MIJIN_UTIL_STRING_HPP_INCLUDED 1 #include #include #include #include #include #include #include "./iterators.hpp" namespace mijin { // // public defines // // // public constants // // // 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 static const TChar SPACE = TChar(' '); } 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 std::basic_string_view trimPrefix(std::basic_string_view stringView, std::basic_string_view charsToTrim = {&detail::SPACE, &detail::SPACE + 1}) { stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size())); return stringView; } template std::basic_string_view trimSuffix(std::basic_string_view stringView, std::basic_string_view charsToTrim = {&detail::SPACE, &detail::SPACE + 1}) { stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size())); return stringView; } template std::basic_string_view trim(std::basic_string_view stringView, std::basic_string_view charsToTrim = {&detail::SPACE, &detail::SPACE + 1}) { return trimPrefix(trimSuffix(stringView, charsToTrim), charsToTrim); } template [[nodiscard]] bool equalsIgnoreCase(TLeft&& left, TRight&& right) noexcept { return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right)); } 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)