header_utils
Loading...
Searching...
No Matches
string_ops.h
1
4
5#pragma once
6
7#include <algorithm>
8#include <vector>
9#include <sstream>
10#include <charconv>
11#include <optional>
12#include <ranges>
13#include <numeric>
14
15#if !defined(__cpp_concepts)
16#error "This library requires concepts"
17#endif
18#if !defined(__cpp_lib_ranges)
19#error "This library requires ranges"
20#endif
21#if defined(__cpp_lib_format)
22#include <format>
23#endif
24#if !defined(__cpp_lib_to_address)
25#error "This library requires std::to_address"
26#endif
27
28
29static_assert(CHAR_BIT >= 8);
30
32{
36
39 template <typename T, typename CHAR_TYPE = char>
40 concept string_or_char = std::is_constructible_v<std::basic_string_view<CHAR_TYPE>, T> || std::is_constructible_v<CHAR_TYPE, T>;
41
44 template <typename T, typename CHAR_TYPE = char>
45 concept stringable = (std::ranges::contiguous_range<T> && std::is_convertible_v<std::ranges::range_value_t<T>, CHAR_TYPE>);
46
48 template <typename T>
49 concept string8 = std::same_as<T, std::string> || std::same_as<T, std::u8string>;
51 template <typename T>
52 concept stringable8 = std::convertible_to<T, std::string_view> || std::convertible_to<T, std::u8string_view>;
54 template <typename T>
55 concept string_view8 = std::same_as<T, std::string_view> || std::same_as<T, std::u8string_view>;
56
58 template <typename T>
59 concept string16 = (sizeof(wchar_t) == sizeof(char16_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u16string>;
61 template <typename T>
62 concept stringable16 = (sizeof(wchar_t) == sizeof(char16_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u16string_view>;
64 template <typename T>
65 concept string_view16 = (sizeof(wchar_t) == sizeof(char16_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u16string_view>;
66
68 template <typename T>
69 concept string32 = (sizeof(wchar_t) == sizeof(char32_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u32string>;
71 template <typename T>
72 concept stringable32 = (sizeof(wchar_t) == sizeof(char32_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u32string_view>;
74 template <typename T>
75 concept string_view32 = (sizeof(wchar_t) == sizeof(char32_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u32string_view>;
76
78 using wide_char16_t = std::conditional_t<sizeof(wchar_t) == sizeof(char16_t), wchar_t, char16_t>;
79
81 using wide_char32_t = std::conditional_t<sizeof(wchar_t) == sizeof(char32_t), wchar_t, char32_t>;
82
83 template <typename A, typename B>
84 concept same_size_and_alignment = sizeof(A) == sizeof(B) && alignof(A) == alignof(B);
85
87 template <typename T>
88 concept charable = (std::is_trivially_copyable_v<T> && (
94 ));
95
97 template <typename T>
98 concept char_type = std::same_as<T, char> || std::same_as<T, wchar_t>;
99
101 template <typename T>
102 concept utf_type = std::same_as<T, char8_t> || std::same_as<T, char16_t> || std::same_as<T, char32_t>;
103
105 template <typename T>
107
109 template <charable T>
110 using charable_utf_t = std::conditional_t<same_size_and_alignment<T, char8_t>, char8_t,
111 std::conditional_t<same_size_and_alignment<T, char16_t>, char16_t,
112 std::conditional_t<same_size_and_alignment<T, char32_t>, char32_t,
113 void
114 >>>;
115
117 template <charable T>
118 using charable_char_t = std::conditional_t<same_size_and_alignment<T, char>, char,
119 std::conditional_t<same_size_and_alignment<T, wide_char16_t>, wide_char16_t,
120 std::conditional_t<same_size_and_alignment<T, wide_char32_t>, wide_char32_t,
121 void
122 >>>;
123
124 template <charable T>
125 using best_stringable_type = std::conditional_t<stringable_base_type<T>,
126 T,
128 >;
129
140
141 template <typename C = char>
142 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::nullptr_t, std::nullptr_t) noexcept { return {}; }
143
144 template <stringable_base_type CT, std::contiguous_iterator IT, std::contiguous_iterator IT2>
146 [[nodiscard]] constexpr auto make_sv(IT start, IT2 end) noexcept(noexcept(std::to_address(start))) {
147 return std::basic_string_view<CT>{
148 reinterpret_cast<CT const*>(std::to_address(start)),
149 static_cast<size_t>(std::to_address(end) - std::to_address(start))
150 };
151 }
152
153 template <std::contiguous_iterator IT, std::contiguous_iterator IT2>
155 [[nodiscard]] constexpr auto make_sv(IT start, IT2 end) noexcept(noexcept(std::to_address(start))) {
157 return make_sv<char_type, IT, IT2>(std::move(start), std::move(end));
158 }
159
160 template <typename T>
162 [[nodiscard]] constexpr auto make_sv(T&& single_char) noexcept {
163 static_assert(!std::is_rvalue_reference_v<decltype(single_char)>, "cannot create string_view's from single char rvalues");
164 return make_sv(&single_char, &single_char + 1);
165 }
166
167 template <charable T>
168 [[nodiscard]] constexpr auto make_sv(const T* str) noexcept {
170 return str ? std::basic_string_view<CT>{ reinterpret_cast<const CT*>(str) } : std::basic_string_view<CT>{};
171 }
172
173 template <typename C>
174 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string_view<C> id) noexcept { return id; }
175 template <typename C>
176 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string<C> const& id) noexcept { return id; }
177 template <typename C>
178 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string<C>&& id) noexcept = delete;
179
180 template <std::ranges::range RANGE>
182 [[nodiscard]] constexpr auto make_sv(RANGE&& range) noexcept
183 {
184 return make_sv(std::ranges::begin(range), std::ranges::end(range));
185 }
186
187 template <typename... NONARGS, typename... ARGS>
188 [[nodiscard]] constexpr auto make_string(ARGS&&... args) {
189 auto sv = make_sv<NONARGS...>(std::forward<ARGS>(args)...);
190 using char_type = typename decltype(sv)::value_type;
191 return std::basic_string<char_type>{ sv };
192 }
193
194 /*
195 template <std::contiguous_iterator IT, std::contiguous_iterator IT2>
196 requires std::is_same_v<std::iter_value_t<IT>, char>&& std::is_same_v<std::iter_value_t<IT2>, char>
197 [[nodiscard]] inline std::string make_string(IT start, IT2 end) noexcept(noexcept(std::to_address(start))) { return std::string{ ::ghassanpl::string_ops::make_sv(start, end) }; }
198 */
199
201
203 template <typename COUT, typename CIN>
205 [[nodiscard]] constexpr std::basic_string_view<COUT> string_view_cast(std::basic_string_view<CIN> id) noexcept
206 {
207 return { reinterpret_cast<COUT const*>(id.data()), id.size() };
208 }
209
214
215 [[nodiscard]] inline std::string to_string(std::string_view from) noexcept { return std::string{ from }; }
216
217 [[nodiscard]] inline std::string to_string(std::u8string_view from) noexcept { return std::string{ from.data(), from.data() + from.size() }; }
218
219 template<typename T>
220 [[nodiscard]] inline std::string to_string(T const& t) requires requires { std::to_string(t); } { return std::to_string(t); }
221
222 [[nodiscard]] constexpr std::string const& to_string(std::same_as<std::string> auto const& s) { return s; }
223
224 template<typename T>
225 [[nodiscard]] inline std::string to_string(std::optional<T> const& o) { if (o.has_value()) return std::to_string(o.value()); return "(empty)"; }
226
228
241 [[nodiscard]] constexpr std::string_view back(std::string_view child_to_back_up, std::string_view parent, size_t n = 1) noexcept
242 {
243 return make_sv(std::max(child_to_back_up.data() - n, parent.data()), child_to_back_up.data() + child_to_back_up.size());
244 }
245
260 [[nodiscard]] constexpr std::string_view back(std::string_view child_to_back_up, size_t n = 1) noexcept
261 {
262 return make_sv(child_to_back_up.data() - n, child_to_back_up.data() + child_to_back_up.size());
263 }
264
266 [[nodiscard]] constexpr bool is_inside(std::string_view big_string, std::string_view smaller_string)
267 {
268 return big_string.data() - smaller_string.data() >= 0 && (smaller_string.data() + smaller_string.size()) - (big_string.data() + big_string.size()) >= 0;
269 }
270
271 namespace ascii
272 {
273
279
284
285#if 0
286 constexpr uint64_t is_flag_set(std::array<uint64_t, 2> flags, char32_t cp) noexcept
287 {
288 const auto flag_el = (cp >> 6) & 1;
289 const auto bit = uint64_t(1) << (cp & 63);
290 return (flags[flag_el] & bit) != 0;
291 }
292
293 template <uint64_t HIGH, uint64_t LOW>
294 constexpr bool is_flag_set(char32_t cp) noexcept
295 {
296 const auto low_mask = uint64_t(0) - (cp >> 6 == 0);
297 const auto high_mask = uint64_t(0) - (cp >> 6 == 1);
298 const auto bit = uint64_t(1) << (cp & 63);
299
300 return ((bit & low_mask) & LOW) or ((bit & high_mask) & HIGH);
301 }
302
303 constexpr std::array<uint64_t, 2> get_flags_for(std::string_view str) noexcept
304 {
305 std::array<uint64_t, 2> result{ 0,0 };
306 for (auto c : str)
307 result[(char32_t(c) >> 6) & 1] |= (char32_t(c) & 63);
308 return result;
309 }
310
311 template <typename FUNC>
312 constexpr std::array<uint64_t, 2> get_flags_for(FUNC&& pred) noexcept
313 {
314 std::array<uint64_t, 2> result{ 0,0 };
315 for (char32_t c = 0; c < 128; ++c)
316 if (pred(c))
317 result[(char32_t(c) >> 6) & 1] |= (char32_t(c) & 63);
318 return result;
319 }
320#endif
321
325
326 [[nodiscard]] constexpr bool isalpha(char32_t cp) noexcept { return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122); }
327 [[nodiscard]] constexpr bool isdigit(char32_t cp) noexcept { return cp >= 48 && cp <= 57; }
328 [[nodiscard]] constexpr bool isodigit(char32_t cp) noexcept { return cp >= 48 && cp <= 55; }
329 [[nodiscard]] constexpr bool isxdigit(char32_t d) noexcept { return (d >= 48 && d <= 57) || (d >= 65 && d <= 70) || (d >= 97 && d <= 102); }
330 [[nodiscard]] constexpr bool isalnum(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isdigit(cp) || ::ghassanpl::string_ops::ascii::isalpha(cp); }
331 [[nodiscard]] constexpr bool isident(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isdigit(cp) || ::ghassanpl::string_ops::ascii::isalpha(cp) || cp == 95; }
332 [[nodiscard]] constexpr bool isidentstart(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isalpha(cp) || cp == 95; }
333 [[nodiscard]] constexpr bool isspace(char32_t cp) noexcept { return (cp >= 9 && cp <= 13) || cp == 32; }
334 [[nodiscard]] constexpr bool ispunct(char32_t cp) noexcept { return (cp >= 33 && cp <= 47) || (cp >= 58 && cp <= 64) || (cp >= 91 && cp <= 96) || (cp >= 123 && cp <= 126); }
335 [[nodiscard]] constexpr bool islower(char32_t cp) noexcept { return cp >= 97 && cp <= 122; }
336 [[nodiscard]] constexpr bool isupper(char32_t cp) noexcept { return cp >= 65 && cp <= 90; }
337 [[nodiscard]] constexpr bool iscntrl(char32_t cp) noexcept { return cp == 0x7F || cp < 0x20; }
338 [[nodiscard]] constexpr bool isblank(char32_t cp) noexcept { return cp == 32 || cp == 9; }
339 [[nodiscard]] constexpr bool isgraph(char32_t cp) noexcept { return cp >= 33 && cp <= 126; }
340 [[nodiscard]] constexpr bool isprint(char32_t cp) noexcept { return cp >= 32 && cp <= 126; }
341
342 [[nodiscard]] constexpr char32_t toupper(char32_t cp) noexcept {
343 return (cp >= 97 && cp <= 122) ? (cp ^ 0b100000) : cp;
344 }
345 [[nodiscard]] constexpr char32_t tolower(char32_t cp) noexcept {
346 return (cp >= 65 && cp <= 90) ? (cp | 0b100000) : cp;
347 }
348
350
351 namespace detail
352 {
353 template <auto FUNC>
354 consteval size_t count_chars() {
355 const auto v = std::views::iota(char32_t{ 1 }, char32_t{ 127 });
356 return std::ranges::count_if(v, FUNC);
357 }
358 template <typename T, auto FUNC>
359 constexpr auto compute_characters_matching() {
360 constexpr size_t char_count = count_chars<FUNC>();
361 std::array<T, char_count> result{};
362 std::ranges::copy_if(std::views::iota(T{ 1 }, T{ 127 }), result.begin(), FUNC);
363 return result;
364 }
365 }
366
370 constexpr inline auto alphabetic_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isalpha>();
371 constexpr inline auto digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isdigit>();
372 constexpr inline auto octal_digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isodigit>();
373 constexpr inline auto hex_digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isxdigit>();
374 constexpr inline auto alphanumeric_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isalnum>();
375 constexpr inline auto identifier_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isident>();
376 constexpr inline auto identifier_start_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isidentstart>();
377 constexpr inline auto whitespace_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isspace>();
378 constexpr inline auto punctuation_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::ispunct>();
379 constexpr inline auto lowercase_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::islower>();
380 constexpr inline auto uppercase_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isupper>();
381 constexpr inline auto control_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::iscntrl>();
382 constexpr inline auto blank_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isblank>();
383 constexpr inline auto graphical_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isgraph>();
384 constexpr inline auto printable_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isprint>();
386
388 template <stringable T>
389 [[nodiscard]] constexpr bool is_identifier(T const& str) noexcept {
390 if (std::ranges::empty(str)) return false;
391 return ::ghassanpl::string_ops::ascii::isidentstart(*std::ranges::begin(str)) && std::ranges::all_of(std::views::drop(str, 1), ::ghassanpl::string_ops::ascii::isident);
392 }
393
395 template <stringable T>
396 [[nodiscard]] constexpr std::string tolower(T const& str) noexcept {
397 using std::ranges::begin;
398 using std::ranges::end;
399 std::string result;
400 if constexpr (std::ranges::sized_range<T>)
401 result.reserve(std::ranges::size(str));
402 std::transform(begin(str), end(str), std::back_inserter(result), [](char cp) { return (char)::ghassanpl::string_ops::ascii::tolower(cp); });
403 return result;
404 }
405
407 [[nodiscard]] constexpr std::string tolower(std::string str) noexcept {
408 std::ranges::transform(str, std::ranges::begin(str), [](char cp) { return (char)::ghassanpl::string_ops::ascii::tolower(cp); });
409 return str;
410 }
411
413 [[nodiscard]] constexpr std::string tolower(const char* str) noexcept {
414 if (str)
415 return tolower(std::string{ str });
416 return {};
417 }
418
420 template <stringable T>
421 [[nodiscard]] inline std::string toupper(T const& str) noexcept {
422 using std::ranges::begin;
423 using std::ranges::end;
424 std::string result;
425 if constexpr (std::ranges::sized_range<T>)
426 result.reserve(std::ranges::size(str));
427 std::transform(begin(str), end(str), std::back_inserter(result), [](char cp) { return (char)::ghassanpl::string_ops::ascii::toupper(cp); });
428 return result;
429 }
430
432 [[nodiscard]] inline std::string toupper(std::string str) noexcept {
433 std::ranges::transform(str, std::ranges::begin(str), [](char cp) { return (char)::ghassanpl::string_ops::ascii::toupper(cp); });
434 return str;
435 }
436
438 [[nodiscard]] inline std::string toupper(const char* str) noexcept {
439 if (str)
440 return toupper(std::string{ str });
441 return {};
442 }
443
445 [[nodiscard]] inline std::string capitalize(std::string str) noexcept {
446 if (str.empty()) return str;
447 str[0] = (char)::ghassanpl::string_ops::ascii::toupper(str[0]);
448 return str;
449 }
450
452 [[nodiscard]] constexpr char32_t number_to_digit(int v) noexcept { return char32_t(v) + 48; }
454 [[nodiscard]] constexpr char32_t number_to_xdigit(int v) noexcept { return (v > 9) ? (char32_t(v - 10) + 65) : (char32_t(v) + 48); }
455
457 [[nodiscard]] constexpr int digit_to_number(char32_t cp) noexcept { return int(cp - 48); }
459 //[[nodiscard]] constexpr int xdigit_to_number(char32_t cp) noexcept { return (cp >= 97 && cp <= 102) ? int(cp - 97) : int((cp >= 65 && cp <= 70) ? (cp - 55) : (cp - 48)); }
460 [[nodiscard]] constexpr int xdigit_to_number(char32_t cp) noexcept { return isdigit(cp) ? int(cp - 48) : ((int(cp) & ~0b100000) - 55); }
461
464 [[nodiscard]] constexpr bool strings_equal_ignore_case(std::string_view sa, std::string_view sb)
465 {
466 return std::ranges::equal(sa, sb, [](char a, char b) { return ::ghassanpl::string_ops::ascii::toupper(a) == ::ghassanpl::string_ops::ascii::toupper(b); });
467 }
468
469 [[nodiscard]] constexpr bool string_starts_with_ignore_case(std::string_view a, std::string_view b)
470 {
471 return strings_equal_ignore_case(a.substr(0, b.size()), b);
472 }
473
474 [[nodiscard]] constexpr bool string_ends_with_ignore_case(std::string_view a, std::string_view b)
475 {
476 if (b.size() > a.size()) return false;
477 return strings_equal_ignore_case(a.substr(a.size() - b.size()), b);
478 }
479
480 [[nodiscard]] constexpr auto string_find_ignore_case(std::string_view haystack, std::string_view pattern)
481 {
482 return std::ranges::search(
483 haystack,
484 pattern,
485 [](char ch1, char ch2) { return ::ghassanpl::string_ops::ascii::tolower(ch1) == ::ghassanpl::string_ops::ascii::tolower(ch2); }
486 ).begin();
487 }
488
489 [[nodiscard]] constexpr auto string_find_last_ignore_case(std::string_view a, std::string_view b)
490 {
491 return std::ranges::find_end(
492 a,
493 b,
494 [](char ch1, char ch2) { return ::ghassanpl::string_ops::ascii::tolower(ch1) == ::ghassanpl::string_ops::ascii::tolower(ch2); }
495 ).begin();
496 }
497
498 [[nodiscard]] constexpr auto string_contains_ignore_case(std::string_view a, std::string_view b)
499 {
500 return string_find_ignore_case(a, b) != a.end();
501 }
502
503 [[nodiscard]] constexpr bool lexicographical_compare_ignore_case(std::string_view first, std::string_view second)
504 {
505 return std::ranges::lexicographical_compare(first, second,
506 [](char a, char b) { return ::ghassanpl::string_ops::ascii::toupper(a) < ::ghassanpl::string_ops::ascii::toupper(b); });
507 }
508
509 [[nodiscard]] constexpr auto lexicographical_compare_ignore_case_three_way(std::string_view a, std::string_view b)
510 {
511 return std::lexicographical_compare_three_way(a.begin(), a.end(), b.begin(), b.end(),
512 [](char ca, char cb) { return ::ghassanpl::string_ops::ascii::toupper(ca) <=> ::ghassanpl::string_ops::ascii::toupper(cb); });
513 }
514
515 namespace detail
516 {
517 struct string_view_case_insensitive
518 {
519 std::string_view value;
520
521 constexpr bool operator ==(std::string_view a) const noexcept { return strings_equal_ignore_case(value, a); }
522 constexpr auto operator <=>(std::string_view a) const noexcept { return lexicographical_compare_ignore_case_three_way(value, a); }
523
524 constexpr bool operator ==(string_view_case_insensitive const& other) const noexcept { return strings_equal_ignore_case(value, other.value); }
525 constexpr auto operator <=>(string_view_case_insensitive const& other) const noexcept { return lexicographical_compare_ignore_case_three_way(value, other.value); }
526 };
527 }
528
530 consteval detail::string_view_case_insensitive operator"" _i(const char* str, size_t size) noexcept { return detail::string_view_case_insensitive{ std::string_view{str, str + size} }; }
531
533
535 }
536
537#pragma push_macro("isascii")
538#undef isascii
540 [[nodiscard]] constexpr bool isascii(char32_t cp) noexcept { return cp <= 127; }
541#pragma pop_macro("isascii")
542
544 [[nodiscard]] constexpr bool is_ascii(char32_t cp) noexcept { return cp <= 127; }
545
547 [[nodiscard]] inline bool contains(std::string_view str, char c)
548 {
549#if __cpp_lib_string_contains
550 return str.contains(c);
551#else
552 return str.find(c) != std::string_view::npos;
553#endif
554 }
555
559 [[nodiscard]] inline std::string_view substr(std::string_view str, intptr_t start, size_t count = std::string::npos) noexcept
560 {
561 if (start < 0)
562 {
563 start = str.size() + start;
564 if (start < 0) start = 0;
565 }
566 if (start >= static_cast<intptr_t>(str.size())) return {};
567 return str.substr(start, static_cast<size_t>(count));
568 }
569
571 [[nodiscard]] inline std::string_view prefix(std::string_view str, size_t count) noexcept {
572 return str.substr(0, count);
573 }
574
576 [[nodiscard]] inline std::string_view without_suffix(std::string_view str, size_t count) noexcept {
577 return str.substr(0, str.size() - std::min(str.size(), count));
578 }
579
581 [[nodiscard]] inline std::string_view suffix(std::string_view str, size_t count) noexcept {
582 return str.substr(str.size() - std::min(count, str.size()));
583 }
584
586 [[nodiscard]] inline std::string_view without_prefix(std::string_view str, size_t count) noexcept {
587 return str.substr(std::min(str.size(), count));
588 }
589
591 [[nodiscard]] inline void erase_outside_n(std::string& str, size_t start, size_t count) noexcept {
592 str.erase(std::min(start + count, str.size()));
593 str.erase(0, std::min(start, str.size()));
594 }
595
598 [[nodiscard]] inline void erase_outside_from_to(std::string& str, size_t from, size_t to) noexcept {
599 const auto from_ = std::min(from, to);
600 const auto to_ = std::max(from, to);
602 }
603
607
608 [[nodiscard]] constexpr std::string_view trimmed_whitespace_right(std::string_view str) noexcept { return make_sv(str.begin(), std::find_if_not(str.rbegin(), str.rend(), ::ghassanpl::string_ops::ascii::isspace).base()); }
609 [[nodiscard]] constexpr std::string_view trimmed_whitespace_left(std::string_view str) noexcept { return make_sv(std::ranges::find_if_not(str, ::ghassanpl::string_ops::ascii::isspace), str.end()); }
610 [[nodiscard]] constexpr std::string_view trimmed_whitespace(std::string_view str) noexcept { return trimmed_whitespace_left(trimmed_whitespace_right(str)); }
611 [[nodiscard]] constexpr std::string_view trimmed_until(std::string_view str, char chr) noexcept { return make_sv(std::ranges::find(str, chr), str.end()); }
612 [[nodiscard]] constexpr std::string_view trimmed(std::string_view str, char chr) noexcept { return make_sv(std::ranges::find_if_not(str, [chr](char c) { return c == chr; }), str.end()); }
613
614 [[nodiscard]] constexpr std::string trimmed_whitespace_right(std::string str) noexcept { str.erase(std::find_if_not(str.rbegin(), str.rend(), ::ghassanpl::string_ops::ascii::isspace).base(), str.end()); return str; }
615 [[nodiscard]] constexpr std::string trimmed_whitespace_left(std::string str) noexcept { str.erase(str.begin(), std::ranges::find_if_not(str, ::ghassanpl::string_ops::ascii::isspace)); return str; }
616 [[nodiscard]] constexpr std::string trimmed_whitespace(std::string str) noexcept { return trimmed_whitespace_left(trimmed_whitespace_right(std::move(str))); }
617 [[nodiscard]] constexpr std::string trimmed_until(std::string str, char chr) noexcept { str.erase(str.begin(), std::ranges::find(str, chr)); return str; }
618 [[nodiscard]] constexpr std::string trimmed(std::string str, char chr) noexcept { str.erase(str.begin(), std::ranges::find_if_not(str, [chr](char c) { return c == chr; })); return str; }
619 //[[nodiscard]] inline std::string_view trimmed(std::string_view str) noexcept { if (!str.empty()) str.remove_prefix(1); return str; }
620 template <typename FUNC>
621 requires std::is_invocable_r_v<bool, FUNC, char>
622 [[nodiscard]] inline std::string_view trimmed_while(std::string_view str, FUNC&& func) noexcept { return ::ghassanpl::string_ops::make_sv(std::find_if_not(str.begin(), str.end(), std::forward<FUNC>(func)), str.end()); }
623
624 constexpr void trim_whitespace_right(std::string_view& str) noexcept { str = make_sv(str.begin(), std::find_if_not(str.rbegin(), str.rend(), ::ghassanpl::string_ops::ascii::isspace).base()); }
625 constexpr void trim_whitespace_left(std::string_view& str) noexcept { str = make_sv(std::ranges::find_if_not(str, ::ghassanpl::string_ops::ascii::isspace), str.end()); }
626 constexpr void trim_whitespace(std::string_view& str) noexcept { trim_whitespace_left(str); trim_whitespace_right(str); }
627 constexpr void trim_until(std::string_view& str, char chr) noexcept { str = trimmed_until(str, chr); }
628 constexpr void trim(std::string_view& str, char chr) noexcept { str = trimmed(str, chr); }
629 template <typename FUNC>
630 requires std::is_invocable_r_v<bool, FUNC, char>
631 constexpr void trim_while(std::string_view& str, FUNC&& func) noexcept { str = trimmed_while(str, std::forward<FUNC>(func)); }
632
634
636 template <std::ranges::random_access_range T>
638 [[nodiscard]] constexpr bool isany(char32_t cp, T&& chars) noexcept {
639 using char_type = std::ranges::range_value_t<T>;
640 return std::ranges::find(chars, static_cast<char_type>(cp)) != std::ranges::end(chars);
641 }
643 constexpr bool isany(char32_t c, char32_t c2) noexcept { return c == c2; }
644
650
652 [[nodiscard]] inline char consume(std::string_view& str)
653 {
654 if (str.empty())
655 return {};
656 const auto result = str[0];
657 str.remove_prefix(1);
658 return result;
659 }
660
663 [[nodiscard]] inline bool consume(std::string_view& str, char val)
664 {
665 if (str.starts_with(val))
666 {
667 str.remove_prefix(1);
668 return true;
669 }
670 return false;
671 }
672
675 [[nodiscard]] inline bool consume(std::string_view& str, std::string_view val)
676 {
677 if (str.starts_with(val))
678 {
679 str.remove_prefix(val.size());
680 return true;
681 }
682 return false;
683 }
684
687 template <typename... ARGS>
688 [[nodiscard]] inline char consume_any(std::string_view& str, ARGS&&... args)
689 {
690 if (!str.empty() && (isany(str[0], args) || ...))
691 {
692 const auto result = str[0];
693 str.remove_prefix(1);
694 return result;
695 }
696 return 0;
697 }
698
701 template <typename PRED>
702 requires std::is_invocable_r_v<bool, PRED, char>
703 [[nodiscard]] inline char consume(std::string_view& str, PRED&& pred)
704 {
705 if (!str.empty() && pred(str[0]))
706 {
707 const auto result = str[0];
708 str.remove_prefix(1);
709 return result;
710 }
711 return {};
712 }
713
715 [[nodiscard]] inline char consume_or(std::string_view& str, char or_else)
716 {
717 if (str.empty())
718 return or_else;
719 const auto result = str[0];
720 str.remove_prefix(1);
721 return result;
722 }
723
727 [[nodiscard]] inline bool consume_at_end(std::string_view& str, char val)
728 {
729 if (str.ends_with(val))
730 {
731 str.remove_suffix(1);
732 return true;
733 }
734 return false;
735 }
736
737
741 [[nodiscard]] inline bool consume_at_end(std::string_view& str, std::string_view val)
742 {
743 if (str.ends_with(val))
744 {
745 str.remove_suffix(val.size());
746 return true;
747 }
748 return false;
749 }
750
753 template <typename FUNC>
754 requires std::is_invocable_r_v<bool, FUNC, char>
755 [[nodiscard]] inline std::string_view consume_while(std::string_view& str, FUNC&& pred)
756 {
757 const auto start = str.begin();
758 while (!str.empty() && pred(str[0]))
759 str.remove_prefix(1);
760 return make_sv(start, str.begin());
761 }
762
765 [[nodiscard]] inline std::string_view consume_while(std::string_view& str, char c)
766 {
767 const auto start = str.begin();
768 while (str.starts_with(c))
769 str.remove_prefix(1);
770 return make_sv(start, str.begin());
771 }
772
775 template <typename... ARGS>
776 [[nodiscard]] inline std::string_view consume_while_any(std::string_view& str, ARGS&&... args)
777 {
778 const auto start = str.begin();
779 while (!str.empty() && (isany(str[0], args) || ...))
780 str.remove_prefix(1);
781 return make_sv(start, str.begin());
782 }
783
786 template <typename FUNC>
787 requires std::is_invocable_r_v<bool, FUNC, char>
788 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, FUNC&& pred)
789 {
790 const auto start = str.begin();
791 while (!str.empty() && !pred(str[0]))
792 str.remove_prefix(1);
793 return make_sv(start, str.begin());
794 }
795
798 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, char c)
799 {
800 const auto start = str.begin();
801 while (!str.empty() && str[0] != c)
802 str.remove_prefix(1);
803 return make_sv(start, str.begin());
804 }
805
808 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, std::string_view end)
809 {
810 auto it = std::ranges::search(str, end).begin();
811 auto result = make_sv(str.begin(), it);
812 str = { it, str.end() };
813 return result;
814 }
815
816 /*
819 template <typename T>
820 requires std::constructible_from<std::string_view, T>
821 [[nodiscard]] inline std::string_view consume_until_any(std::string_view& str, T&& chars)
822 {
823 const auto charssv = std::string_view{ chars };
824 const auto start = str.begin();
825 while (!str.empty() && !charssv.contains(str[0]))
826 str.remove_prefix(1);
827 return make_sv(start, str.begin());
828 }
829 */
830
833 template <typename... ARGS>
834 [[nodiscard]] inline std::string_view consume_until_any(std::string_view& str, ARGS&&... args)
835 {
836 const auto start = str.begin();
837 while (!str.empty() && !(isany(str[0], args) || ...))
838 str.remove_prefix(1);
839 return make_sv(start, str.begin());
840 }
841
844 [[nodiscard]] inline std::string_view consume_until_delim(std::string_view& str, char c)
845 {
847
848 const auto start = str.begin();
849 while (!str.empty() && str[0] != c)
850 str.remove_prefix(1);
851 std::ignore = consume(str, c);
852 return make_sv(start, str.begin());
853 }
854
857 [[nodiscard]] inline std::string_view consume_n(std::string_view& str, size_t n)
858 {
859 n = std::min(str.size(), n);
860 auto result = str.substr(0, n);
861 str.remove_prefix(n);
862 return result;
863 }
864
867 template <typename FUNC>
868 requires std::is_invocable_r_v<bool, FUNC, char>
869 [[nodiscard]] inline std::string_view consume_n(std::string_view& str, size_t n, FUNC&& pred)
870 {
871 n = std::min(str.size(), n);
872 const auto start = str.begin();
873 while (n-- && !str.empty() && pred(str[0]))
874 str.remove_prefix(1);
875 return make_sv(start, str.begin());
876 }
877
893 template <typename CALLBACK>
894 requires std::is_invocable_r_v<bool, CALLBACK, std::string_view&>
895 [[nodiscard]] inline bool consume_delimited_list_non_empty(std::string_view& str, std::string_view delimiter, CALLBACK callback)
896 {
897 do
898 {
899 trim_whitespace_left(str);
900 if (!callback(str))
901 return false;
902 trim_whitespace_left(str);
903 } while (consume(str, delimiter));
904 return true;
905 }
906
922 template <typename CALLBACK>
923 requires std::is_invocable_r_v<bool, CALLBACK, std::string_view>
924 [[nodiscard]] inline bool consume_delimited_list(std::string_view& str, std::string_view delimiter, std::string_view closer, CALLBACK callback)
925 {
926 trim_whitespace_left(str);
927 while (!str.empty())
928 {
929 trim_whitespace_left(str);
930 if (!callback(str))
931 return false;
932 trim_whitespace_left(str);
933 if (!consume(str, delimiter))
934 return consume(str, closer);
935 }
936 return false;
937 }
938
940
945
949
953 template <typename FUNC>
954 requires std::is_invocable_v<FUNC, std::string_view, bool>
955 constexpr void split(std::string_view source, char delim, FUNC&& func) noexcept(noexcept(func(std::string_view{}, true)))
956 {
957 size_t next = 0;
958 while ((next = source.find_first_of(delim)) != std::string::npos)
959 {
960 func(source.substr(0, next), false);
961 source.remove_prefix(next + 1);
962 }
963 func(source, true);
964 }
965
969 template <typename FUNC>
970 requires std::is_invocable_v<FUNC, std::string_view, bool>
971 constexpr void split(std::string_view source, std::string_view delim, FUNC&& func) noexcept(noexcept(func(std::string_view{}, true)))
972 {
973 const size_t delim_size = delim.size();
974 if (delim_size == 0) return;
975
976 size_t next = 0;
977 while ((next = source.find(delim)) != std::string::npos)
978 {
979 func(source.substr(0, next), false);
980 source.remove_prefix(next + delim_size);
981 }
982 func(source, true);
983 }
984
988 template <typename FUNC>
989 requires std::is_invocable_v<FUNC, std::string_view, bool>
990 constexpr void split_on_any(std::string_view source, std::string_view delim, FUNC&& func) noexcept(noexcept(func(std::string_view{}, true)))
991 {
993 if (delim.empty()) return;
994
995 size_t next = 0;
996 while ((next = source.find_first_of(std::string_view{ delim })) != std::string::npos)
997 {
998 func(source.substr(0, next), false);
999 source.remove_prefix(next + 1);
1000 }
1001 func(source, true);
1002 }
1003
1018 template <typename DELIM_FUNC, typename FUNC>
1019 requires std::is_invocable_v<FUNC, std::string_view, bool>&& std::is_invocable_r_v<size_t, DELIM_FUNC, std::string_view>
1020 inline void split_on(std::string_view source, DELIM_FUNC&& delim, FUNC&& func) noexcept(noexcept(func(std::string_view{}, true)) && noexcept(delim(std::string_view{})))
1021 {
1022 size_t start = 0;
1023 size_t end = 0;
1024 end = delim(source);
1025 while (end != std::string::npos)
1026 {
1027 func(source.substr(start, end - start), false);
1028 //source.remove_prefix(end);
1029 start = end;
1030 auto next = delim(source.substr(end + 1));
1031 if (next == std::string::npos)
1032 break;
1033 end = next + end + 1;
1034 }
1035 func(source.substr(start), true);
1036 }
1037
1039 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> split_at(std::string_view src, size_t split_at) noexcept
1040 {
1041 if (split_at == std::string::npos)
1042 return { src, {} };
1043 return { src.substr(0, split_at), src.substr(split_at + 1) };
1044 }
1045
1047 [[nodiscard]] constexpr bool split_at(std::string_view src, size_t split_at, std::string_view& first, std::string_view& second) noexcept
1048 {
1049 if (split_at == std::string::npos)
1050 return false;
1051 first = src.substr(0, split_at);
1052 second = src.substr(split_at + 1);
1053 return true;
1054 }
1055
1058 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> single_split(std::string_view src, char delim) noexcept
1059 {
1060 return split_at(src, src.find_first_of(delim));
1061 }
1062
1065 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> single_split_last(std::string_view src, char delim) noexcept
1066 {
1067 return split_at(src, src.find_last_of(delim));
1068 }
1069
1074 [[nodiscard]] constexpr bool single_split(std::string_view src, char delim, std::string_view& first, std::string_view& second) noexcept
1075 {
1076 return split_at(src, src.find_first_of(delim), first, second);
1077 }
1078
1083 [[nodiscard]] constexpr bool single_split_last(std::string_view src, char delim, std::string_view& first, std::string_view& second) noexcept
1084 {
1085 return split_at(src, src.find_last_of(delim), first, second);
1086 }
1087
1099 template <typename FUNC>
1100 requires std::is_invocable_v<FUNC, std::string_view, bool>
1101 inline void natural_split(std::string_view source, char delim, FUNC&& func) noexcept
1102 {
1103 size_t next = 0;
1104 while ((next = source.find_first_of(delim)) != std::string::npos)
1105 {
1106 func(source.substr(0, next), false);
1107 source.remove_prefix(next + 1);
1108
1109 if ((next = source.find_first_not_of(delim)) == std::string::npos)
1110 return;
1111
1112 source.remove_prefix(next);
1113 }
1114
1115 if (!source.empty())
1116 func(source, true);
1117 }
1118
1122 template <typename RESULT_TYPE = std::string_view, string_or_char DELIM>
1123 [[nodiscard]] constexpr std::vector<RESULT_TYPE> split(std::string_view source, DELIM&& delim) noexcept
1124 {
1125 std::vector<RESULT_TYPE> result;
1126 ::ghassanpl::string_ops::split(source, std::forward<DELIM>(delim), [&result](std::string_view str, bool) {
1127 result.push_back(RESULT_TYPE{ str });
1128 });
1129 return result;
1130 }
1131
1135 template <typename RESULT_TYPE = std::string_view>
1136 [[nodiscard]] constexpr std::vector<RESULT_TYPE> split_on_any(std::string_view source, std::string_view delim) noexcept
1137 {
1139 std::vector<RESULT_TYPE> result;
1140 ::ghassanpl::string_ops::split_on_any(source, delim, [&result](std::string_view str, bool) {
1141 result.push_back(RESULT_TYPE{ str });
1142 });
1143 return result;
1144 }
1145
1146
1150 template <typename RESULT_TYPE = std::string_view, typename DELIM_FUNC>
1151 requires std::is_invocable_r_v<size_t, DELIM_FUNC, std::string_view>
1152 [[nodiscard]] std::vector<RESULT_TYPE> split_on(std::string_view source, DELIM_FUNC&& delim) noexcept(noexcept(delim(std::string_view{})))
1153 {
1154 std::vector<RESULT_TYPE> result;
1155 ::ghassanpl::string_ops::split_on(source, std::forward<DELIM_FUNC>(delim), [&result](std::string_view str, bool last) {
1156 result.push_back(RESULT_TYPE{ str });
1157 });
1158 return result;
1159 }
1160
1163 template <typename RESULT_TYPE = std::string_view, string_or_char DELIM>
1164 [[nodiscard]] inline std::vector<RESULT_TYPE> natural_split(std::string_view source, DELIM&& delim) noexcept
1165 {
1166 std::vector<RESULT_TYPE> result;
1167 ::ghassanpl::string_ops::natural_split(source, std::forward<DELIM>(delim), [&result](std::string_view str, bool last) {
1168 result.push_back(RESULT_TYPE{ str });
1169 });
1170 return result;
1171 }
1172
1174
1180
1182 template <std::ranges::range T>
1183 [[nodiscard]] inline auto join(T&& source)
1184 {
1185 std::stringstream strm;
1186 for (auto&& p : std::forward<T>(source))
1187 strm << p;
1188 return strm.str();
1189 }
1190
1192 template <std::ranges::range T, string_or_char DELIM>
1193 [[nodiscard]] inline auto join(T&& source, DELIM const& delim)
1194 {
1195 std::stringstream strm;
1196 bool first = true;
1197 for (auto&& p : std::forward<T>(source))
1198 {
1199 if (!first) strm << delim;
1200 strm << p;
1201 first = false;
1202 }
1203 return strm.str();
1204 }
1205
1207 template <std::ranges::range... RANGES, string_or_char DELIM>
1208 [[nodiscard]] inline auto join_multiple(DELIM const& delim, RANGES&&... sources)
1209 {
1210 std::stringstream strm;
1211 bool first = true;
1212 ([&]<typename RANGE>(RANGE && source) {
1213 for (auto&& p : std::forward<RANGE>(source))
1214 {
1215 if (!first) strm << delim;
1216 strm << p;
1217 first = false;
1218 }
1219 }(std::forward<RANGES>(sources)), ...);
1220 return strm.str();
1221 }
1222
1223
1226 template <std::ranges::range T, string_or_char DELIM, string_or_char LAST_DELIM>
1227 [[nodiscard]] inline auto join_and(T&& source, DELIM const& delim, LAST_DELIM&& last_delim)
1228 {
1229 using std::ranges::begin;
1230 using std::ranges::end;
1231 using std::next;
1232
1233 std::stringstream strm;
1234 bool first = true;
1235
1236 auto&& endit = end(source);
1237 for (auto it = begin(source); it != endit; ++it)
1238 {
1239 if (!first)
1240 {
1241 if (next(it) == endit)
1243 else
1244 strm << delim;
1245 }
1246 strm << *it;
1247 first = false;
1248 }
1249 return strm.str();
1250 }
1251
1252
1256 template <std::ranges::range T, string_or_char DELIM, string_or_char LAST_DELIM, typename FUNC>
1257 [[nodiscard]] inline auto join_and(T&& source, DELIM const& delim, LAST_DELIM&& last_delim, FUNC&& transform_func)
1258 {
1259 using std::ranges::begin;
1260 using std::ranges::end;
1261 using std::next;
1262
1263 std::stringstream strm;
1264 bool first = true;
1265
1266 auto&& endit = end(source);
1267 for (auto it = begin(source); it != endit; ++it)
1268 {
1269 if (!first)
1270 {
1271 if (next(it) == endit)
1273 else
1274 strm << delim;
1275 }
1276 strm << transform_func(*it);
1277 first = false;
1278 }
1279 return strm.str();
1280 }
1281
1285 template <std::ranges::range T, typename FUNC, string_or_char DELIM>
1286 [[nodiscard]] inline auto join(T&& source, DELIM const& delim, FUNC&& transform_func)
1287 {
1288 std::stringstream strm;
1289 bool first = true;
1290 for (auto&& p : source)
1291 {
1292 if (!first) strm << delim;
1293 strm << transform_func(p);
1294 first = false;
1295 }
1296 return strm.str();
1297 }
1298
1300
1301 template <string_or_char NEEDLE, typename FUNC>
1302 inline void find_all(std::string_view subject, NEEDLE&& search, FUNC&& func)
1303 {
1304 const auto search_sv = make_sv(search);
1305
1306 if (search_sv.empty())
1307 return;
1308
1309 size_t pos = 0;
1310 while ((pos = subject.find(search, pos)) != std::string::npos)
1311 {
1312 func(subject.substr(pos, search_sv.size()));
1313 pos += search_sv.size();
1314 }
1315 }
1316
1317 template <typename RESULT_TYPE = std::string_view, string_or_char NEEDLE>
1318 inline std::vector<RESULT_TYPE> find_all(std::string_view subject, NEEDLE&& search)
1319 {
1320 std::vector<RESULT_TYPE> result;
1321 find_all(subject, std::forward<NEEDLE>(search), [&](std::string_view sv) { result.push_back(RESULT_TYPE{ sv }); });
1322 return result;
1323 }
1324
1330
1331 template <string_or_char NEEDLE, string_or_char REPLACE>
1332 inline void replace(std::string& subject, NEEDLE&& search, REPLACE&& replace)
1333 {
1334 using std::empty;
1335 using std::size;
1336
1337 const auto search_sv = make_sv(search);
1338 const auto replace_sv = make_sv(replace);
1339
1340 if (search_sv.empty())
1341 return;
1342
1343 size_t pos = 0;
1344 while ((pos = subject.find(search, pos)) != std::string::npos)
1345 {
1346 subject.replace(pos, search_sv.size(), replace_sv);
1347 pos += replace_sv.size();
1348 }
1349 }
1350
1351 template <string_or_char NEEDLE, string_or_char REPLACE>
1352 [[nodiscard]] inline std::string replaced(std::string subject, NEEDLE&& search, REPLACE&& replace)
1353 {
1354 string_ops::replace(subject, std::forward<NEEDLE>(search), std::forward<REPLACE>(replace));
1355 return subject;
1356 }
1357
1358 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1359 inline void quote(std::string& subject, DELIMITER delimiter = '"', ESCAPE escape = '\\')
1360 {
1361 const char replace_delim[]{ escape, delimiter, '\0' };
1362 const char replace_escape[]{ escape, escape, '\0' };
1363 if constexpr (requires { delimiter != escape; })
1364 {
1365 if (delimiter != escape)
1366 ::ghassanpl::string_ops::replace(subject, escape, replace_escape);
1367 }
1368 else
1369 ::ghassanpl::string_ops::replace(subject, escape, replace_escape);
1370 ::ghassanpl::string_ops::replace(subject, delimiter, replace_delim);
1371 subject.insert(subject.begin(), delimiter);
1372 subject += delimiter;
1373 }
1374
1375 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1376 [[nodiscard]] inline std::string quoted(std::string&& subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1377 {
1378 ::ghassanpl::string_ops::quote(subject, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1379 return subject;
1380 }
1381
1382 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1383 [[nodiscard]] inline std::string quoted(std::string_view subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1384 {
1385 auto result = std::string{ subject };
1386 ::ghassanpl::string_ops::quote(result, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1387 return result;
1388 }
1389
1390 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1391 [[nodiscard]] inline std::string quoted(const char* subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1392 {
1393 return ::ghassanpl::string_ops::quoted(std::string{ subject }, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1394 }
1395
1396 template <typename ESCAPE_FUNC>
1397 requires std::is_invocable_v<ESCAPE_FUNC, std::string_view>&& std::is_constructible_v<std::string_view, std::invoke_result_t<ESCAPE_FUNC, std::string_view>>
1398 inline void escape(std::string& subject, std::string_view chars_to_escape, ESCAPE_FUNC&& escape_func)
1399 {
1400 if (chars_to_escape.empty())
1401 return;
1402
1403 size_t pos = 0;
1404 while ((pos = subject.find_first_of(chars_to_escape, pos)) != std::string::npos)
1405 {
1406 auto escape_str = escape_func(subject.substr(pos, 1));
1407 subject.replace(pos, 1, escape_str);
1408 pos += escape_str.size();
1409 }
1410 }
1411
1412 template <string_or_char ESCAPE = char>
1413 inline void escape(std::string& subject, std::string_view chars_to_escape, ESCAPE&& escape = '\\')
1414 {
1415 if (chars_to_escape.empty())
1416 return;
1417
1418 auto escape_str = make_sv(escape);
1419
1420 size_t pos = 0;
1421 while ((pos = subject.find_first_of(chars_to_escape, pos)) != std::string::npos)
1422 {
1423 subject.insert(pos, escape_str);
1424 pos += escape_str.size() + 1;
1425 }
1426 }
1427
1428 template <typename ESCAPE_FUNC, typename ISPRINTABLE_FUNC = decltype(ascii::isprint)>
1429 inline void escape_non_printable(std::string& subject, ESCAPE_FUNC&& escape_func, ISPRINTABLE_FUNC&& isprintable_func = ascii::isprint)
1430 {
1431 static_assert(std::is_invocable_v<ESCAPE_FUNC, char> && std::is_constructible_v<std::string_view, std::invoke_result_t<ESCAPE_FUNC, char>>,
1432 "escape function must be invocable with (char) and must return something convertible to string_view");
1433 static_assert(std::is_invocable_v<ISPRINTABLE_FUNC, char> && std::is_constructible_v<bool, std::invoke_result_t<ISPRINTABLE_FUNC, char>>,
1434 "isprintable function must be a predicate invocable with (char)");
1435
1436 size_t pos = 0;
1437 std::string::iterator it;
1438 while ((it = std::find_if_not(subject.begin() + pos, subject.end(), isprintable_func)) != subject.end())
1439 {
1440 pos = it - subject.begin();
1442 subject.replace(pos, 1, escape_str);
1443 pos += escape_str.size();
1444 }
1445 }
1446
1447 inline void escape_non_printable(std::string& subject)
1448 {
1449 escape_non_printable(subject, [](char c) {
1450 return std::format("\\x{:02x}", static_cast<uint8_t>(c));
1451 });
1452 }
1453
1454 template <typename STR, string_or_char ESCAPE = char>
1455 [[nodiscard]] inline std::string escaped(STR&& subject, std::string_view to_escape = "\"\\", ESCAPE&& escape_str = '\\')
1456 {
1457 auto result = std::string{ subject };
1458 ::ghassanpl::string_ops::escape(result, to_escape, std::forward<ESCAPE>(escape_str));
1459 return result;
1460 }
1461
1462 template <typename STR>
1463 [[nodiscard]] inline std::string escaped_non_printable(STR&& subject)
1464 {
1465 auto result = std::string{ subject };
1466 ::ghassanpl::string_ops::escape_non_printable(result);
1467 return result;
1468 }
1469
1471
1473 [[nodiscard]] inline std::string url_encode(std::string_view text)
1474 {
1475 std::string result;
1476 for (auto c : text)
1477 {
1478 if (ascii::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
1479 {
1480 result += c;
1481 continue;
1482 }
1483
1484 result += '%';
1485 std::format_to(std::back_inserter(result), "{:02X}", (int)(unsigned char)c);
1486 }
1487 return result;
1488 }
1489
1491 [[nodiscard]] inline std::string url_unencode(std::string_view text)
1492 {
1493 std::string result;
1494 while (!text.empty())
1495 {
1496 if (text[0] == '%')
1497 {
1498 text.remove_prefix(1);
1499 if (text.size() < 2)
1500 continue;
1501 uint8_t val{};
1502 std::from_chars(text.data(), text.data() + 2, val, 16);
1503 result += (char)val;
1504 text.remove_prefix(2);
1505 continue;
1506 }
1507
1508 result += text[0];
1509 text.remove_prefix(1);
1510 }
1511 return result;
1512 }
1513
1514#if defined(__cpp_lib_format)
1515
1518 template <typename CHECKER>
1519 requires std::predicate<CHECKER, std::string_view>
1520 [[nodiscard]] inline std::string unique_name(std::string_view base_name, CHECKER&& checker)
1521 {
1522 std::string new_name = std::string{ base_name };
1523 size_t index = 1;
1524 while (!checker(std::string_view{ new_name }))
1525 new_name = std::format("{}{}", base_name, index++);
1526 return new_name;
1527 }
1528#endif
1529
1530#if !__cpp_lib_to_chars && !defined(DOXYGEN)
1531
1532#else
1534 template <std::integral T>
1535 [[nodiscard]] inline auto from_chars(std::string_view str, T& value, const int base = 10) noexcept {
1536 return std::from_chars(str.data(), str.data() + str.size(), value, base);
1537 }
1539 template <std::floating_point T>
1540 [[nodiscard]] inline auto from_chars(std::string_view str, T& value, const std::chars_format chars_format = std::chars_format::general) noexcept {
1541 return std::from_chars(str.data(), str.data() + str.size(), value, chars_format);
1542 }
1543#endif
1544
1551 template <bool SINGLE>
1553 {
1554 split_range(std::string_view source, char split_on) : mSource(source), mSplit(split_on) {}
1555
1556 struct split_range_iterator
1557 {
1558 const char* RangeStart;
1559 const char* RangeEnd;
1560 const char* SourceEnd;
1561 char SplitChar;
1562
1563 bool operator!=(const split_range_iterator& other) const { return RangeStart != other.SourceEnd; }
1564 split_range_iterator& operator++(int) {
1565 auto copy = *this;
1566 ++*this;
1567 return copy;
1568 }
1569
1570 split_range_iterator operator++() {
1571 auto rs = RangeEnd;
1572 auto se = SourceEnd;
1573 auto sc = SplitChar;
1574
1575 if (SINGLE)
1576 {
1577 if (rs < se && *rs == sc)
1578 ++rs;
1579 }
1580 else
1581 {
1582 while (rs < se && *rs == sc)
1583 ++rs;
1584 }
1585
1586 RangeStart = rs;
1587
1588 while (rs < se && *rs != sc)
1589 ++rs;
1590
1591 RangeEnd = rs;
1592 return *this;
1593 }
1594
1595 std::pair<const char*, const char*> operator*() const { return { RangeStart, RangeEnd }; }
1596 };
1597
1598 split_range_iterator begin() {
1599 split_range_iterator it = { nullptr, mSource.data(), mSource.data() + mSource.size(), mSplit };
1600 ++it;
1601 return it;
1602 }
1603
1604 split_range_iterator end() {
1605 auto se = mSource.data() + mSource.size();
1606 return { se, se, se, mSplit };
1607 }
1608
1610 auto source() const { return mSource; }
1612 auto split_on() const { return mSplit; }
1613
1614 private:
1615
1616 std::string_view mSource;
1617 char mSplit;
1618 };
1619
1620 // static_assert(std::ranges::range<split_range<true>>);
1621
1629 template <typename RESULT_TYPE = std::string_view, typename T, typename FUNC>
1630 requires std::is_arithmetic_v<T>&& std::is_invocable_r_v<T, FUNC, std::string_view>
1631 std::vector<RESULT_TYPE> word_wrap(std::string_view _source, T max_width, FUNC width_getter)
1632 {
1633 std::vector<RESULT_TYPE> result;
1634
1635 auto space_left = max_width;
1636 auto space_width = width_getter(" ");
1637
1638 for (auto line : split_range<true>(_source, '\n'))
1639 {
1640 auto line_start = line.first;
1641
1642 for (auto r : split_range<false>({ line.first, size_t(line.second - line.first) }, ' '))
1643 {
1644 const auto word_width = width_getter({ r.first, size_t(r.second - r.first) });
1645 const auto width = word_width + space_width;
1646 if (width > space_left)
1647 {
1648 result.push_back(RESULT_TYPE{ line_start, size_t(r.first - line_start) - 1 });
1650 line_start = r.first;
1651 }
1652 else
1653 {
1654 space_left -= width;
1655 }
1656 }
1657
1658 result.push_back(RESULT_TYPE{ line_start, size_t(line.second - line_start) });
1659 }
1660
1661 return result;
1662 }
1663
1666 template <typename RESULT_TYPE = std::string_view, typename T>
1667 requires std::is_arithmetic_v<T>
1668 std::vector<RESULT_TYPE> word_wrap(std::string_view _source, T max_width, T letter_width)
1669 {
1670 return ::ghassanpl::string_ops::word_wrap<RESULT_TYPE>(_source, max_width, [letter_width](std::string_view str) { return T(str.size() * letter_width); });
1671 }
1672
1673 inline size_t levenshtein_distance(std::string_view s1, std::string_view s2)
1674 {
1675 if (s1.size() > s2.size())
1676 std::swap(s1, s2);
1677
1678 const auto min_size = s1.size();
1679 const auto max_size = s2.size();
1680
1681 std::vector<size_t> lev_dist(min_size + 1);
1682 for (size_t i = 0; i < lev_dist.size(); ++i)
1683 lev_dist[i] = i;
1684
1685 for (size_t j = 1; j <= max_size; ++j)
1686 {
1687 size_t previous_diagonal = lev_dist[0];
1688 size_t previous_diagonal_save{};
1689 ++lev_dist[0];
1690
1691 for (size_t i = 1; i <= min_size; ++i)
1692 {
1694 if (s1[i - 1] == s2[j - 1])
1696 else
1697 lev_dist[i] = std::min({ lev_dist[i - 1], lev_dist[i], previous_diagonal }) + 1;
1699 }
1700 }
1701
1702 return lev_dist[min_size];
1703 }
1704
1705 namespace detail
1706 {
1707 template <std::integral T>
1708 [[nodiscard]] inline auto string_to_number(std::string_view str, size_t* idx = nullptr, int base = 10)
1709 {
1710 const auto begin = str.data();
1711 auto end = str.data() + str.size();
1712 T value{};
1713 if (auto res = std::from_chars(begin, end, value, base); idx && res.ec == std::error_code{})
1714 *idx = size_t(res.ptr - begin);
1715 return value;
1716 }
1717
1718 template <std::floating_point T>
1719 [[nodiscard]] inline auto string_to_number(std::string_view str, size_t* idx = nullptr, std::chars_format format = std::chars_format::general)
1720 {
1721 const auto begin = str.data();
1722 auto end = str.data() + str.size();
1723 T value{};
1724 if (auto res = std::from_chars(begin, end, value, format); idx && res.ec == std::error_code{})
1725 *idx = size_t(res.ptr - begin);
1726 return value;
1727 }
1728 }
1729
1733 [[nodiscard]] inline int stoi(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<int>(str, idx, base); }
1734 [[nodiscard]] inline long stol(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<long>(str, idx, base); }
1735 [[nodiscard]] inline long long stoll(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<long long>(str, idx, base); }
1736 [[nodiscard]] inline unsigned long stoul(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<unsigned long>(str, idx, base); }
1737 [[nodiscard]] inline unsigned long long stoull(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<unsigned long long>(str, idx, base); }
1738 [[nodiscard]] inline float stof(std::string_view str, size_t* idx = nullptr, std::chars_format format = std::chars_format::general) { return detail::string_to_number<float>(str, idx, format); }
1739 [[nodiscard]] inline double stod(std::string_view str, size_t* idx = nullptr, std::chars_format format = std::chars_format::general) { return detail::string_to_number<double>(str, idx, format); }
1740 [[nodiscard]] inline long double stold(std::string_view str, size_t* idx = nullptr, std::chars_format format = std::chars_format::general) { return detail::string_to_number<long double>(str, idx, format); }
1742
1743 template <typename CALLBACK>
1744 requires std::invocable<CALLBACK, size_t, std::string_view, std::string&>
1745 std::string callback_format(std::string_view fmt, CALLBACK&& callback)
1746 {
1748 std::string result;
1749 size_t aid = 0;
1750 while (!fmt.empty())
1751 {
1752 auto text = consume_until(fmt, '{');
1753 result += text;
1754 if (fmt.empty())
1755 continue;
1756 std::ignore = consume(fmt, '{');
1757
1758 if (consume(fmt, '{'))
1759 {
1760 result += '{';
1761 continue;
1762 }
1763 else
1764 {
1765 std::string fmt_clause_str = "{";
1768 throw std::format_error("missing '}' in format string");
1769 if (!fmt_clause.empty())
1770 {
1772 if (!num.empty())
1773 aid = string_ops::stoull(num);
1775 }
1776 fmt_clause_str += '}';
1777 callback(aid, std::string_view{ fmt_clause_str }, result);
1778 ++aid;
1779 }
1780 }
1781 return result;
1782 }
1783
1785#define GHPL_FORMAT_TEMPLATE typename... GHPL_ARGS
1787#define GHPL_FORMAT_ARGS std::string_view ghpl_fmt, GHPL_ARGS&&... ghpl_args
1789#define GHPL_FORMAT_FORWARD ghpl_fmt, std::forward<GHPL_ARGS>(ghpl_args)...
1791#define GHPL_FORMAT_CALL std::vformat(ghpl_fmt, std::make_format_args(std::forward<GHPL_ARGS>(ghpl_args)...))
1793#define GHPL_PRINT_CALL std::vprint_unicode(ghpl_fmt, std::make_format_args(std::forward<GHPL_ARGS>(ghpl_args)...))
1794
1796}
Whether the type is a native char type.
Definition string_ops.h:98
Can a type be bit-cast to a native/utf char type?
Definition string_ops.h:88
The type is a string with a 16-bit char type.
Definition string_ops.h:59
The type is a string with an 32-bit char type.
Definition string_ops.h:69
The type is a string with an 8-bit char type.
Definition string_ops.h:49
The type is a stringable or a character.
Definition string_ops.h:40
The type is a string view with a 16-bit char type.
Definition string_ops.h:65
The type is a string view with a 32-bit char type.
Definition string_ops.h:75
The type is a string view with an 8-bit char type.
Definition string_ops.h:55
The type is convertible to a string view with a 16-bit char type.
Definition string_ops.h:62
The type is convertible to a string view with a 32-bit char type.
Definition string_ops.h:72
The type is convertible to a string view with an 8-bit char type.
Definition string_ops.h:52
Whether the type is a native or utf char type.
Definition string_ops.h:106
The type is "stringable", that is, a continuous range of characters.
Definition string_ops.h:45
Whether the type is a utf char type.
Definition string_ops.h:102
constexpr char32_t number_to_digit(int v) noexcept
Convert a number between 0 and 9 to its ASCII representation (only gives meaningful results with argu...
Definition string_ops.h:452
constexpr auto hex_digit_chars
All characters that match ascii::isxdigit.
Definition string_ops.h:373
constexpr auto blank_chars
All characters that match ascii::isblank.
Definition string_ops.h:382
constexpr auto control_chars
All characters that match ascii::iscntrl.
Definition string_ops.h:381
constexpr auto punctuation_chars
All characters that match ascii::ispunct.
Definition string_ops.h:378
constexpr auto lowercase_chars
All characters that match ascii::islower.
Definition string_ops.h:379
constexpr auto alphanumeric_chars
All characters that match ascii::isalnum.
Definition string_ops.h:374
constexpr auto whitespace_chars
All characters that match ascii::isspace.
Definition string_ops.h:377
constexpr auto printable_chars
All characters that match ascii::isprint.
Definition string_ops.h:384
constexpr int digit_to_number(char32_t cp) noexcept
Convert an ASCII digit character to its numerical value (only gives meaningful results with valid dig...
Definition string_ops.h:457
constexpr char32_t number_to_xdigit(int v) noexcept
Convert a number between 0 and 15 to its ASCII representation (only gives meaningful results with arg...
Definition string_ops.h:454
constexpr auto digit_chars
All characters that match ascii::isdigit.
Definition string_ops.h:371
constexpr auto identifier_start_chars
All characters that match ascii::isidentstart.
Definition string_ops.h:376
constexpr auto uppercase_chars
All characters that match ascii::isupper.
Definition string_ops.h:380
constexpr auto alphabetic_chars
All characters that match ascii::isalpha.
Definition string_ops.h:370
constexpr auto octal_digit_chars
All characters that match ascii::isodigit.
Definition string_ops.h:372
constexpr bool is_identifier(T const &str) noexcept
Returns true if the given str is a C-style identifier (e.g. matches /[\w_][\w_0-9]+/)
Definition string_ops.h:389
constexpr int xdigit_to_number(char32_t cp) noexcept
Convert an ASCII xdigit to its numerical value (only gives meaningful results with valid xdigit argum...
Definition string_ops.h:460
constexpr auto graphical_chars
All characters that match ascii::isgraph.
Definition string_ops.h:383
std::string capitalize(std::string str) noexcept
Returns a copy of the string with the first letter transformed to upper case if possible.
Definition string_ops.h:445
constexpr auto identifier_chars
All characters that match ascii::isident.
Definition string_ops.h:375
constexpr auto bit_count
Equal to the number of bits in the type.
Definition bits.h:33
constexpr bool is_flag_set(INTEGRAL const &bits, ENUM_TYPE flag) noexcept
Checks if an integral value has the bit at number represented by flag set.
Definition flag_bits.h:64
constexpr CONTAINER to(RANGE &&range, TYPES &&... args)
to<container>();
Definition ranges.h:369
constexpr __contains_fn contains
contains(range, el)
Definition ranges.h:247
constexpr bool isascii(char32_t cp) noexcept
Returns true if cp is an ascii codepoint.
Definition string_ops.h:540
char consume_or(std::string_view &str, char or_else)
Consumes the first character from str, returning it, or or_else if string is empty.
Definition string_ops.h:715
std::string_view consume_while(std::string_view &str, FUNC &&pred)
Consumes characters from the beginning of str while they match pred(str[0]).
Definition string_ops.h:755
std::conditional_t< same_size_and_alignment< T, char >, char, std::conditional_t< same_size_and_alignment< T, wide_char16_t >, wide_char16_t, std::conditional_t< same_size_and_alignment< T, wide_char32_t >, wide_char32_t, void > > > charable_char_t
The native char type corresponding to the charable type.
Definition string_ops.h:122
void natural_split(std::string_view source, char delim, FUNC &&func) noexcept
Performs a more natural split of the string, that is: ignoring multiple delimiters in a row,...
bool consume_delimited_list_non_empty(std::string_view &str, std::string_view delimiter, CALLBACK callback)
Consumes a list of delimiter-delimited strings, calling callback(str) each time; whitespaces before a...
Definition string_ops.h:895
auto from_chars(std::string_view str, T &value, const int base=10) noexcept
A version of std::from_chars that takes a std::string_view as the first argument.
std::string_view suffix(std::string_view str, size_t count) noexcept
Returns a substring containing the count rightmost characters of str. Always valid,...
Definition string_ops.h:581
char consume_any(std::string_view &str, ARGS &&... args)
Consumes any of the characters in 'chars' if it's the first char of str.
Definition string_ops.h:688
constexpr std::pair< std::string_view, std::string_view > single_split_last(std::string_view src, char delim) noexcept
Splits src once on the last instance of delim
std::string_view consume_until(std::string_view &str, FUNC &&pred)
Consumes characters from the beginning of str until one matches pred(str[0]), exclusive.
Definition string_ops.h:788
constexpr bool is_inside(std::string_view big_string, std::string_view smaller_string)
Checks if smaller_string is a true subset of big_string (true subset meaning they view over overlappi...
Definition string_ops.h:266
std::conditional_t< same_size_and_alignment< T, char8_t >, char8_t, std::conditional_t< same_size_and_alignment< T, char16_t >, char16_t, std::conditional_t< same_size_and_alignment< T, char32_t >, char32_t, void > > > charable_utf_t
The utf char type corresponding to the charable type.
Definition string_ops.h:114
constexpr std::basic_string_view< COUT > string_view_cast(std::basic_string_view< CIN > id) noexcept
Casts a string_view to a string_view with a different char type via a simple reinterpret_cast.
Definition string_ops.h:205
constexpr void split(std::string_view source, char delim, FUNC &&func) noexcept(noexcept(func(std::string_view{}, true)))
Performs a basic "split" operation, calling func for each part of source delimited by delim.
Definition string_ops.h:955
std::conditional_t< sizeof(wchar_t)==sizeof(char16_t), wchar_t, char16_t > wide_char16_t
The default 16-bit char type for the current platform (wchar_t if it is 16-bit, char16_t otherwise)
Definition string_ops.h:78
std::string escaped(STR &&subject, std::string_view to_escape="\"\\", ESCAPE &&escape_str='\\')
Lint Note: Changing the initializer of to_escape to a R-string breaks doxygen.
std::string_view consume_while_any(std::string_view &str, ARGS &&... args)
Consumes a run of any of the characters in 'chars' at the beginning of str.
Definition string_ops.h:776
char consume(std::string_view &str)
Consumes and returns the first character in the str, or \0 if no more characters.
Definition string_ops.h:652
std::conditional_t< sizeof(wchar_t)==sizeof(char32_t), wchar_t, char32_t > wide_char32_t
The default 32-bit char type for the current platform (wchar_t if it is 32-bit, char32_t otherwise)
Definition string_ops.h:81
void erase_outside_from_to(std::string &str, size_t from, size_t to) noexcept
Erases all characters in str outside of the range [from, to].
Definition string_ops.h:598
constexpr void split_on_any(std::string_view source, std::string_view delim, FUNC &&func) noexcept(noexcept(func(std::string_view{}, true)))
Performs a basic "split" operation, calling func for each part of source delimited by any character i...
Definition string_ops.h:990
std::string_view consume_until_any(std::string_view &str, ARGS &&... args)
Consumes characters from the beginning of str until one is equal to any in the parameter pack,...
Definition string_ops.h:834
std::string url_unencode(std::string_view text)
Returns a url-decoded version of the string.
auto join_and(T &&source, DELIM const &delim, LAST_DELIM &&last_delim)
Returns a string that is created by joining together string representation of the elements in the sou...
constexpr bool isany(char32_t cp, T &&chars) noexcept
Checks if cp is any of the characters in chars
Definition string_ops.h:638
std::string_view consume_until_delim(std::string_view &str, char c)
Consumes characters from the beginning of str until one is equal to c, inclusive.
Definition string_ops.h:844
auto join(T &&source)
Returns a string that is created by joining together string representation of the elements in the sou...
std::string url_encode(std::string_view text)
Returns a url-encoded version of the string.
std::vector< RESULT_TYPE > word_wrap(std::string_view _source, T max_width, FUNC width_getter)
Performs a basic word-wrapping split of _source, as if it was constrained to max_width.
auto join_multiple(DELIM const &delim, RANGES &&... sources)
Returns a string that is created by joining together string representation of the elements in the sou...
std::string_view without_suffix(std::string_view str, size_t count) noexcept
Returns a substring created by removing count characters from the end. Always valid,...
Definition string_ops.h:576
void erase_outside_n(std::string &str, size_t start, size_t count) noexcept
Erases all characters in str outside of the range [start, start + count]. Always safe.
Definition string_ops.h:591
constexpr std::pair< std::string_view, std::string_view > single_split(std::string_view src, char delim) noexcept
Splits src once on the first instance of delim
constexpr bool is_ascii(char32_t cp) noexcept
Returns true if cp is an ascii codepoint.
Definition string_ops.h:544
constexpr std::string_view back(std::string_view child_to_back_up, std::string_view parent, size_t n=1) noexcept
Creates a string_view with its beginning moved back by n characters, limited to a parent range.
Definition string_ops.h:241
bool consume_at_end(std::string_view &str, char val)
Consumes the last character from str if it matches val.
Definition string_ops.h:727
constexpr std::pair< std::string_view, std::string_view > split_at(std::string_view src, size_t split_at) noexcept
Does not include the character at split_at in the returned strings.
std::string_view substr(std::string_view str, intptr_t start, size_t count=std::string::npos) noexcept
Gets a substring of str starting at start and containing count characters.
Definition string_ops.h:559
std::string_view without_prefix(std::string_view str, size_t count) noexcept
Returns a substring created by removing count characters from the start. Always valid,...
Definition string_ops.h:586
std::string_view prefix(std::string_view str, size_t count) noexcept
Returns a substring containing the count leftmost characters of str. Always valid,...
Definition string_ops.h:571
std::string_view consume_n(std::string_view &str, size_t n)
Consumes at most n characters from the beginning of str.
Definition string_ops.h:857
bool consume_delimited_list(std::string_view &str, std::string_view delimiter, std::string_view closer, CALLBACK callback)
Consumes a list of delimiter-delimited strings, ended with closer, calling callback(str) each time; w...
Definition string_ops.h:924
void split_on(std::string_view source, DELIM_FUNC &&delim, FUNC &&func) noexcept(noexcept(func(std::string_view{}, true)) &&noexcept(delim(std::string_view{})))
Performs a basic "split" operation, calling func for each part of source delimited by the delim funct...
The below code is based on Sun's libm library code, which is licensed under the following license:
A very basic "range" (not really a C++ range yet) that can be iterated over as if its a range of elem...
auto source() const
Returns the source string we're splitting.
auto split_on() const
Returns the character we're splitting on.