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 <array>
14#include <numeric>
15
16#if !defined(__cpp_concepts)
17#error "This library requires concepts"
18#endif
19#if !defined(__cpp_lib_ranges)
20#error "This library requires ranges"
21#endif
22#if defined(__cpp_lib_format)
23#include <format>
24#endif
25#if !defined(__cpp_lib_to_address)
26#error "This library requires std::to_address"
27#endif
28
29#include "expected.h"
30
31static_assert(CHAR_BIT >= 8);
32
34{
38
41 template <typename T, typename CHAR_TYPE = char>
42 concept string_or_char = std::is_constructible_v<std::basic_string_view<CHAR_TYPE>, T> || std::is_constructible_v<CHAR_TYPE, T>;
43
46 template <typename T, typename CHAR_TYPE = char>
47 concept stringable = (std::ranges::contiguous_range<T> && std::is_convertible_v<std::ranges::range_value_t<T>, CHAR_TYPE>);
48
50 template <typename T>
51 concept string8 = std::same_as<T, std::string> || std::same_as<T, std::u8string>;
53 template <typename T>
54 concept stringable8 = std::convertible_to<T, std::string_view> || std::convertible_to<T, std::u8string_view>;
56 template <typename T>
57 concept string_view8 = std::same_as<T, std::string_view> || std::same_as<T, std::u8string_view>;
58
60 template <typename T>
61 concept string16 = (sizeof(wchar_t) == sizeof(char16_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u16string>;
63 template <typename T>
64 concept stringable16 = (sizeof(wchar_t) == sizeof(char16_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u16string_view>;
66 template <typename T>
67 concept string_view16 = (sizeof(wchar_t) == sizeof(char16_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u16string_view>;
68
70 template <typename T>
71 concept string32 = (sizeof(wchar_t) == sizeof(char32_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u32string>;
73 template <typename T>
74 concept stringable32 = (sizeof(wchar_t) == sizeof(char32_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u32string_view>;
76 template <typename T>
77 concept string_view32 = (sizeof(wchar_t) == sizeof(char32_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u32string_view>;
78
80 using wide_char16_t = std::conditional_t<sizeof(wchar_t) == sizeof(char16_t), wchar_t, char16_t>;
81
83 using wide_char32_t = std::conditional_t<sizeof(wchar_t) == sizeof(char32_t), wchar_t, char32_t>;
84
85 template <typename A, typename B>
86 concept same_size_and_alignment = sizeof(A) == sizeof(B) && alignof(A) == alignof(B);
87
89 template <typename T>
90 concept charable = (std::is_trivially_copyable_v<T> && (
91 same_size_and_alignment<T, wchar_t> ||
92 same_size_and_alignment<T, char> ||
93 same_size_and_alignment<T, char8_t> ||
94 same_size_and_alignment<T, char16_t> ||
95 same_size_and_alignment<T, char32_t>
96 ));
97
99 template <typename T>
100 concept char_type = std::same_as<T, char> || std::same_as<T, wchar_t>;
101
103 template <typename T>
104 concept utf_type = std::same_as<T, char8_t> || std::same_as<T, char16_t> || std::same_as<T, char32_t>;
105
107 template <typename T>
109
111 template <charable T>
112 using charable_utf_t = std::conditional_t<same_size_and_alignment<T, char8_t>, char8_t,
113 std::conditional_t<same_size_and_alignment<T, char16_t>, char16_t,
114 std::conditional_t<same_size_and_alignment<T, char32_t>, char32_t,
115 void
116 >>>;
117
119 template <charable T>
120 using charable_char_t = std::conditional_t<same_size_and_alignment<T, char>, char,
121 std::conditional_t<same_size_and_alignment<T, wide_char16_t>, wide_char16_t,
122 std::conditional_t<same_size_and_alignment<T, wide_char32_t>, wide_char32_t,
123 void
124 >>>;
125
126 template <charable T>
127 using best_stringable_type = std::conditional_t<stringable_base_type<T>,
128 T,
130 >;
131
142
143 template <typename C = char>
144 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::nullptr_t, std::nullptr_t) noexcept { return {}; }
145
146 template <stringable_base_type CT, std::contiguous_iterator IT, std::contiguous_iterator IT2>
147 requires charable<std::iter_value_t<IT>>
148 [[nodiscard]] constexpr auto make_sv(IT start, IT2 end) noexcept(noexcept(std::to_address(start))) {
149 return std::basic_string_view<CT>{
150 reinterpret_cast<CT const*>(std::to_address(start)),
151 static_cast<size_t>(std::to_address(end) - std::to_address(start))
152 };
153 }
154
155 template <std::contiguous_iterator IT, std::contiguous_iterator IT2>
157 [[nodiscard]] constexpr auto make_sv(IT start, IT2 end) noexcept(noexcept(std::to_address(start))) {
158 using char_type = best_stringable_type<std::iter_value_t<IT>>;
159 return make_sv<char_type, IT, IT2>(std::move(start), std::move(end));
160 }
161
162 template <typename T>
164 [[nodiscard]] constexpr auto make_sv(T&& single_char) noexcept {
165 static_assert(!std::is_rvalue_reference_v<decltype(single_char)>, "cannot create string_view's from single char rvalues");
166 return make_sv(&single_char, &single_char + 1);
167 }
168
169 template <charable T>
170 [[nodiscard]] constexpr auto make_sv(const T* str) noexcept {
171 using CT = best_stringable_type<T>;
172 return str ? std::basic_string_view<CT>{ reinterpret_cast<const CT*>(str) } : std::basic_string_view<CT>{};
173 }
174
175 template <typename C>
176 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string_view<C> id) noexcept { return id; }
177 template <typename C>
178 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string<C> const& id) noexcept { return id; }
179 template <typename C>
180 [[nodiscard]] constexpr std::basic_string_view<C> make_sv(std::basic_string<C>&& id) noexcept = delete;
181
182 template <std::ranges::range RANGE>
184 [[nodiscard]] constexpr auto make_sv(RANGE&& range) noexcept
185 {
186 return make_sv(std::ranges::begin(range), std::ranges::end(range));
187 }
188
189 template <typename... NONARGS, typename... ARGS>
190 [[nodiscard]] constexpr auto make_string(ARGS&&... args) {
191 auto sv = make_sv<NONARGS...>(std::forward<ARGS>(args)...);
192 using char_type = typename decltype(sv)::value_type;
193 return std::basic_string<char_type>{ sv };
194 }
195
196 /*
197 template <std::contiguous_iterator IT, std::contiguous_iterator IT2>
198 requires std::is_same_v<std::iter_value_t<IT>, char>&& std::is_same_v<std::iter_value_t<IT2>, char>
199 [[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) }; }
200 */
201
203
205 template <typename COUT, typename CIN>
206 requires charable<COUT> && charable<CIN> && same_size_and_alignment<COUT, CIN>
207 [[nodiscard]] constexpr std::basic_string_view<COUT> string_view_cast(std::basic_string_view<CIN> id) noexcept
208 {
209 return { reinterpret_cast<COUT const*>(id.data()), id.size() };
210 }
211
216
217 [[nodiscard]] inline std::string to_string(std::string_view from) noexcept { return std::string{ from }; }
218 [[nodiscard]] inline std::string string_view_to_string(std::string_view from) noexcept { return std::string{ from }; }
219
220 [[nodiscard]] inline std::string to_string(std::u8string_view from) noexcept { return std::string{ from.data(), from.data() + from.size() }; }
221
222 template<typename T>
223 [[nodiscard]] inline std::string to_string(T const& t) requires requires { std::to_string(t); } { return std::to_string(t); }
224
225 [[nodiscard]] constexpr std::string const& to_string(std::same_as<std::string> auto const& s) { return s; }
226
227 template<typename T>
228 [[nodiscard]] inline std::string to_string(std::optional<T> const& o) { if (o.has_value()) return std::to_string(o.value()); return "(empty)"; }
229
231
244 [[nodiscard]] constexpr std::string_view back(std::string_view child_to_back_up, std::string_view parent, size_t n = 1) noexcept
245 {
246 return make_sv(std::max(child_to_back_up.data() - n, parent.data()), child_to_back_up.data() + child_to_back_up.size());
247 }
248
263 [[nodiscard]] constexpr std::string_view back(std::string_view child_to_back_up, size_t n = 1) noexcept
264 {
265 return make_sv(child_to_back_up.data() - n, child_to_back_up.data() + child_to_back_up.size());
266 }
267
269 [[nodiscard]] constexpr bool is_inside(std::string_view big_string, std::string_view smaller_string)
270 {
271 return smaller_string.data() - big_string.data() >= 0 && (smaller_string.data() + smaller_string.size()) - (big_string.data() + big_string.size()) < 0;
272 }
273
274 namespace ascii
275 {
276
282
287
288#if 0
289 constexpr uint64_t is_flag_set(std::array<uint64_t, 2> flags, char32_t cp) noexcept
290 {
291 const auto flag_el = (cp >> 6) & 1;
292 const auto bit = uint64_t(1) << (cp & 63);
293 return (flags[flag_el] & bit) != 0;
294 }
295
296 template <uint64_t HIGH, uint64_t LOW>
297 constexpr bool is_flag_set(char32_t cp) noexcept
298 {
299 const auto low_mask = uint64_t(0) - (cp >> 6 == 0);
300 const auto high_mask = uint64_t(0) - (cp >> 6 == 1);
301 const auto bit = uint64_t(1) << (cp & 63);
302
303 return ((bit & low_mask) & LOW) or ((bit & high_mask) & HIGH);
304 }
305
306 constexpr void set_flag(std::array<uint64_t, 2>& flags, char32_t cp) noexcept
307 {
308 const auto flag_el = (cp >> 6) & 1;
309 const auto bit = uint64_t(1) << (cp & 63);
310 flags[flag_el] |= bit;
311 }
312
313 constexpr std::array<uint64_t, 2> get_flags_for(std::string_view str) noexcept
314 {
315 std::array<uint64_t, 2> result{ 0,0 };
316 for (auto c : str)
317 set_flag(result, c);
318 return result;
319 }
320
321 template <typename FUNC>
322 constexpr std::array<uint64_t, 2> get_flags_for(FUNC&& pred) noexcept
323 {
324 std::array<uint64_t, 2> result{ 0,0 };
325 for (char32_t c = 0; c < 128; ++c)
326 if (pred(c))
327 set_flag(result, c);
328 return result;
329 }
330#endif
331
339
340 [[nodiscard]] constexpr bool isalpha(char32_t cp) noexcept { return (cp >= 65 && cp <= 90) || (cp >= 97 && cp <= 122); }
341 [[nodiscard]] constexpr bool isdigit(char32_t cp) noexcept { return cp >= 48 && cp <= 57; }
342 [[nodiscard]] constexpr bool isodigit(char32_t cp) noexcept { return cp >= 48 && cp <= 55; }
343 [[nodiscard]] constexpr bool isxdigit(char32_t d) noexcept { return (d >= 48 && d <= 57) || (d >= 65 && d <= 70) || (d >= 97 && d <= 102); }
344 [[nodiscard]] constexpr bool isXdigit(char32_t d) noexcept { return (d >= 48 && d <= 57) || (d >= 65 && d <= 70); }
345 [[nodiscard]] constexpr bool islxdigit(char32_t d) noexcept { return (d >= 48 && d <= 57) || (d >= 97 && d <= 102); }
346 [[nodiscard]] constexpr bool isalnum(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isdigit(cp) || ::ghassanpl::string_ops::ascii::isalpha(cp); }
347 [[nodiscard]] constexpr bool isident(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isdigit(cp) || ::ghassanpl::string_ops::ascii::isalpha(cp) || cp == 95; }
348 [[nodiscard]] constexpr bool isidentstart(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::isalpha(cp) || cp == 95; }
349 [[nodiscard]] constexpr bool isspace(char32_t cp) noexcept { return (cp >= 9 && cp <= 13) || cp == 32; }
350 [[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); }
351 [[nodiscard]] constexpr bool islower(char32_t cp) noexcept { return cp >= 97 && cp <= 122; }
352 [[nodiscard]] constexpr bool isupper(char32_t cp) noexcept { return cp >= 65 && cp <= 90; }
353 [[nodiscard]] constexpr bool iscntrl(char32_t cp) noexcept { return cp == 0x7F || cp < 0x20; }
354 [[nodiscard]] constexpr bool isblank(char32_t cp) noexcept { return cp == 32 || cp == 9; }
355 [[nodiscard]] constexpr bool isgraph(char32_t cp) noexcept { return cp >= 33 && cp <= 126; }
356 [[nodiscard]] constexpr bool isprint(char32_t cp) noexcept { return cp >= 32 && cp <= 126; }
357
358 [[nodiscard]] constexpr char32_t toupper(char32_t cp) noexcept {
359 return (cp >= 97 && cp <= 122) ? (cp ^ 0b100000) : cp;
360 }
361 [[nodiscard]] constexpr char32_t tolower(char32_t cp) noexcept {
362 return (cp >= 65 && cp <= 90) ? (cp | 0b100000) : cp;
363 }
364
365 namespace extended
366 {
367 [[nodiscard]] constexpr bool iscntrl1(char32_t cp) noexcept { return (cp >= 0x7E && cp <= 0x9F); }
368 [[nodiscard]] constexpr bool iscntrl(char32_t cp) noexcept { return ::ghassanpl::string_ops::ascii::iscntrl(cp) || iscntrl1(cp); }
369 }
370
372
373 namespace detail
374 {
375 template <auto FUNC>
376 consteval size_t count_chars() {
377 const auto v = std::views::iota(char32_t{ 1 }, char32_t{ 127 });
378 return std::ranges::count_if(v, FUNC);
379 }
380 template <typename T, auto FUNC>
381 constexpr auto compute_characters_matching() {
382 constexpr size_t char_count = count_chars<FUNC>();
383 std::array<T, char_count> result{};
384 std::ranges::copy_if(std::views::iota(T{ 1 }, T{ 127 }), result.begin(), FUNC);
385 return result;
386 }
387 }
388
392 constexpr inline auto alphabetic_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isalpha>();
393 constexpr inline auto digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isdigit>();
394 constexpr inline auto octal_digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isodigit>();
395 constexpr inline auto hex_digit_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isxdigit>();
396 constexpr inline auto alphanumeric_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isalnum>();
397 constexpr inline auto identifier_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isident>();
398 constexpr inline auto identifier_start_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isidentstart>();
399 constexpr inline auto whitespace_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isspace>();
400 constexpr inline auto punctuation_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::ispunct>();
401 constexpr inline auto lowercase_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::islower>();
402 constexpr inline auto uppercase_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isupper>();
403 constexpr inline auto control_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::iscntrl>();
404 constexpr inline auto blank_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isblank>();
405 constexpr inline auto graphical_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isgraph>();
406 constexpr inline auto printable_chars = detail::compute_characters_matching<char, ::ghassanpl::string_ops::ascii::isprint>();
408
410 template <stringable T>
411 [[nodiscard]] constexpr bool is_identifier(T const& str) noexcept {
412 if (std::ranges::empty(str)) return false;
413 return ::ghassanpl::string_ops::ascii::isidentstart(*std::ranges::begin(str)) && std::ranges::all_of(std::views::drop(str, 1), ::ghassanpl::string_ops::ascii::isident);
414 }
415
417 template <stringable T>
418 [[nodiscard]] constexpr std::string tolower(T const& str) noexcept {
419 using std::ranges::begin;
420 using std::ranges::end;
421 std::string result;
422 if constexpr (std::ranges::sized_range<T>)
423 result.reserve(std::ranges::size(str));
424 std::transform(begin(str), end(str), std::back_inserter(result), [](char cp) { return (char)::ghassanpl::string_ops::ascii::tolower(cp); });
425 return result;
426 }
427
429 [[nodiscard]] constexpr std::string tolower(std::string str) noexcept {
430 std::ranges::transform(str, std::ranges::begin(str), [](char cp) { return (char)::ghassanpl::string_ops::ascii::tolower(cp); });
431 return str;
432 }
433
435 [[nodiscard]] constexpr std::string tolower(const char* str) noexcept {
436 if (str)
437 return tolower(std::string{ str });
438 return {};
439 }
440
442 template <stringable T>
443 [[nodiscard]] inline std::string toupper(T const& str) noexcept {
444 using std::ranges::begin;
445 using std::ranges::end;
446 std::string result;
447 if constexpr (std::ranges::sized_range<T>)
448 result.reserve(std::ranges::size(str));
449 std::transform(begin(str), end(str), std::back_inserter(result), [](char cp) { return (char)::ghassanpl::string_ops::ascii::toupper(cp); });
450 return result;
451 }
452
454 [[nodiscard]] inline std::string toupper(std::string str) noexcept {
455 std::ranges::transform(str, std::ranges::begin(str), [](char cp) { return (char)::ghassanpl::string_ops::ascii::toupper(cp); });
456 return str;
457 }
458
460 [[nodiscard]] inline std::string toupper(const char* str) noexcept {
461 if (str)
462 return toupper(std::string{ str });
463 return {};
464 }
465
467 [[nodiscard]] inline std::string capitalize(std::string str) noexcept {
468 if (str.empty()) return str;
469 str[0] = (char)::ghassanpl::string_ops::ascii::toupper(str[0]);
470 return str;
471 }
472
474 [[nodiscard]] constexpr char32_t number_to_digit(int v) noexcept { return char32_t(v) + 48; }
476 [[nodiscard]] constexpr char32_t number_to_xdigit(int v) noexcept { return (v > 9) ? (char32_t(v - 10) + 65) : (char32_t(v) + 48); }
477
479 [[nodiscard]] constexpr int digit_to_number(char32_t cp) noexcept { return int(cp - 48); }
481 //[[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)); }
482 [[nodiscard]] constexpr int xdigit_to_number(char32_t cp) noexcept { return isdigit(cp) ? int(cp - 48) : ((int(cp) & ~0b100000) - 55); }
483
487 [[nodiscard]] constexpr bool strings_equal_ignore_case(std::string_view sa, std::string_view sb)
488 {
489 return std::ranges::equal(sa, sb, [](char a, char b) { return ::ghassanpl::string_ops::ascii::toupper(a) == ::ghassanpl::string_ops::ascii::toupper(b); });
490 }
491
492 [[nodiscard]] constexpr bool string_starts_with_ignore_case(std::string_view a, std::string_view b)
493 {
494 return strings_equal_ignore_case(a.substr(0, b.size()), b);
495 }
496
497 [[nodiscard]] constexpr bool string_ends_with_ignore_case(std::string_view a, std::string_view b)
498 {
499 if (b.size() > a.size()) return false;
500 return strings_equal_ignore_case(a.substr(a.size() - b.size()), b);
501 }
502
503 [[nodiscard]] constexpr auto string_find_ignore_case(std::string_view haystack, std::string_view pattern)
504 {
505 return std::ranges::search(
506 haystack,
507 pattern,
508 [](char ch1, char ch2) { return ::ghassanpl::string_ops::ascii::toupper(ch1) == ::ghassanpl::string_ops::ascii::toupper(ch2); }
509 ).begin();
510 }
511
512 [[nodiscard]] constexpr auto string_find_last_ignore_case(std::string_view a, std::string_view b)
513 {
514 return std::ranges::find_end(
515 a,
516 b,
517 [](char ch1, char ch2) { return ::ghassanpl::string_ops::ascii::toupper(ch1) == ::ghassanpl::string_ops::ascii::toupper(ch2); }
518 ).begin();
519 }
520
521 [[nodiscard]] constexpr auto string_contains_ignore_case(std::string_view a, std::string_view b)
522 {
523 return string_find_ignore_case(a, b) != a.end();
524 }
525
526 [[nodiscard]] constexpr bool lexicographical_compare_ignore_case(std::string_view first, std::string_view second)
527 {
528 return std::ranges::lexicographical_compare(first, second,
529 [](char a, char b) { return ::ghassanpl::string_ops::ascii::toupper(a) < ::ghassanpl::string_ops::ascii::toupper(b); });
530 }
531
532 [[nodiscard]] constexpr auto lexicographical_compare_ignore_case_three_way(std::string_view a, std::string_view b)
533 {
534 return std::lexicographical_compare_three_way(a.begin(), a.end(), b.begin(), b.end(),
535 [](char ca, char cb) { return ::ghassanpl::string_ops::ascii::toupper(ca) <=> ::ghassanpl::string_ops::ascii::toupper(cb); });
536 }
537
538 [[nodiscard]] constexpr uint64_t hash_ignore_case(std::string_view str)
539 {
541 uint64_t result = 0xcbf29ce484222325;
542 for (auto byte : str)
543 result = (result ^ static_cast<uint8_t>(::ghassanpl::string_ops::ascii::toupper(byte))) * 0x00000100000001b3U;
544 return result;
545 }
546
547 namespace detail
548 {
549 struct string_view_case_insensitive
550 {
551 std::string_view value;
552
553 constexpr bool operator ==(std::string_view a) const noexcept { return strings_equal_ignore_case(value, a); }
554 constexpr auto operator <=>(std::string_view a) const noexcept { return lexicographical_compare_ignore_case_three_way(value, a); }
555
556 constexpr bool operator ==(string_view_case_insensitive const& other) const noexcept { return strings_equal_ignore_case(value, other.value); }
557 constexpr auto operator <=>(string_view_case_insensitive const& other) const noexcept { return lexicographical_compare_ignore_case_three_way(value, other.value); }
558 };
559 }
560
562 [[nodiscard]] 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} }; }
563
565
567 }
568
569#pragma push_macro("isascii")
570#undef isascii
572 [[nodiscard]] constexpr bool isascii(char32_t cp) noexcept { return cp <= 127; }
573#pragma pop_macro("isascii")
574
576 [[nodiscard]] constexpr bool is_ascii(char32_t cp) noexcept { return cp <= 127; }
577
579 [[nodiscard]] constexpr bool string_contains(std::string_view str, char c)
580 {
581#if __cpp_lib_string_contains
582 return str.contains(c);
583#else
584 return str.find(c) != std::string_view::npos;
585#endif
586 }
587
588 [[nodiscard]] constexpr auto data_end(std::string_view str) noexcept { return str.data() + str.size(); }
589
593 [[nodiscard]] inline std::string_view substr(std::string_view str, intptr_t start, size_t count = std::string::npos) noexcept
594 {
595 if (start < 0)
596 {
597 start = str.size() + start;
598 if (start < 0) start = 0;
599 }
600 if (start >= static_cast<intptr_t>(str.size())) return {};
601 return str.substr(start, static_cast<size_t>(count));
602 }
603
605 [[nodiscard]] inline std::string_view prefix(std::string_view str, size_t count) noexcept {
606 return str.substr(0, count);
607 }
608
610 [[nodiscard]] inline std::string_view without_suffix(std::string_view str, size_t count) noexcept {
611 return str.substr(0, str.size() - std::min(str.size(), count));
612 }
613
615 [[nodiscard]] inline std::string_view suffix(std::string_view str, size_t count) noexcept {
616 return str.substr(str.size() - std::min(count, str.size()));
617 }
618
620 [[nodiscard]] inline std::string_view without_prefix(std::string_view str, size_t count) noexcept {
621 return str.substr(std::min(str.size(), count));
622 }
623
625 inline void erase_outside_n(std::string& str, size_t start, size_t count) noexcept {
626 str.erase(std::min(start + count, str.size()));
627 str.erase(0, std::min(start, str.size()));
628 }
629
632 inline void erase_outside_from_to(std::string& str, size_t from, size_t to) noexcept {
633 const auto from_ = std::min(from, to);
634 const auto to_ = std::max(from, to);
635 erase_outside_n(str, from_, to_ - from_);
636 }
637
640 inline void remove_suffix(std::string& str, size_t prefix_size) noexcept
641 {
642 str.erase(str.size() - std::min(str.size(), prefix_size));
643 }
644
647 inline void remove_prefix(std::string& str, size_t prefix_size) noexcept
648 {
649 str.erase(0, std::min(str.size(), prefix_size));
650 }
651
655
656 [[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()); }
657 [[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()); }
658 [[nodiscard]] constexpr std::string_view trimmed_whitespace(std::string_view str) noexcept { return trimmed_whitespace_left(trimmed_whitespace_right(str)); }
659 [[nodiscard]] constexpr std::string_view trimmed_until(std::string_view str, char chr) noexcept { return make_sv(std::ranges::find(str, chr), str.end()); }
660 [[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()); }
661
662 [[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; }
663 [[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; }
664 [[nodiscard]] constexpr std::string trimmed_whitespace(std::string str) noexcept { return trimmed_whitespace_left(trimmed_whitespace_right(std::move(str))); }
665 [[nodiscard]] constexpr std::string trimmed_until(std::string str, char chr) noexcept { str.erase(str.begin(), std::ranges::find(str, chr)); return str; }
666 [[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; }
667 //[[nodiscard]] inline std::string_view trimmed(std::string_view str) noexcept { if (!str.empty()) str.remove_prefix(1); return str; }
668 template <typename FUNC>
669 requires std::is_invocable_r_v<bool, FUNC, char>
670 [[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()); }
671
673
674 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()); }
675 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()); }
676 constexpr void trim_whitespace(std::string_view& str) noexcept { trim_whitespace_left(str); trim_whitespace_right(str); }
677 constexpr void trim_until(std::string_view& str, char chr) noexcept { str = trimmed_until(str, chr); }
678 constexpr void trim(std::string_view& str, char chr) noexcept { str = trimmed(str, chr); }
679 template <typename FUNC>
680 requires std::is_invocable_r_v<bool, FUNC, char>
681 constexpr void trim_while(std::string_view& str, FUNC&& func) noexcept { str = trimmed_while(str, std::forward<FUNC>(func)); }
682
684
686 template <std::ranges::random_access_range T>
688 [[nodiscard]] constexpr bool isany(char32_t cp, T&& chars) noexcept {
689 using char_type = std::ranges::range_value_t<T>;
690 return std::ranges::find(chars, static_cast<char_type>(cp)) != std::ranges::end(chars);
691 }
692
693 constexpr bool isany(char32_t c, char32_t c2) noexcept { return c == c2; }
694
700
702 [[nodiscard]] inline char consume(std::string_view& str)
703 {
704 if (str.empty())
705 return {};
706 const auto result = str[0];
707 str.remove_prefix(1);
708 return result;
709 }
710
713 [[nodiscard]] inline bool consume(std::string_view& str, char val)
714 {
715 if (str.starts_with(val))
716 {
717 str.remove_prefix(1);
718 return true;
719 }
720 return false;
721 }
722
725 [[nodiscard]] inline bool consume(std::string_view& str, std::string_view val)
726 {
727 if (str.starts_with(val))
728 {
729 str.remove_prefix(val.size());
730 return true;
731 }
732 return false;
733 }
734
737 template <typename... ARGS>
738 [[nodiscard]] inline char consume_any(std::string_view& str, ARGS&&... args)
739 {
740 if (!str.empty() && (isany(str[0], args) || ...))
741 {
742 const auto result = str[0];
743 str.remove_prefix(1);
744 return result;
745 }
746 return 0;
747 }
748
751 template <typename PRED>
752 requires std::is_invocable_r_v<bool, PRED, char>
753 [[nodiscard]] inline char consume(std::string_view& str, PRED&& pred)
754 {
755 if (!str.empty() && pred(str[0]))
756 {
757 const auto result = str[0];
758 str.remove_prefix(1);
759 return result;
760 }
761 return {};
762 }
763
765 [[nodiscard]] inline char consume_or(std::string_view& str, char or_else)
766 {
767 if (str.empty())
768 return or_else;
769 const auto result = str[0];
770 str.remove_prefix(1);
771 return result;
772 }
773
777 [[nodiscard]] inline bool consume_at_end(std::string_view& str, char val)
778 {
779 if (str.ends_with(val))
780 {
781 str.remove_suffix(1);
782 return true;
783 }
784 return false;
785 }
786
787
791 [[nodiscard]] inline bool consume_at_end(std::string_view& str, std::string_view val)
792 {
793 if (str.ends_with(val))
794 {
795 str.remove_suffix(val.size());
796 return true;
797 }
798 return false;
799 }
800
803 template <typename FUNC>
804 requires std::is_invocable_r_v<bool, FUNC, char>
805 [[nodiscard]] inline std::string_view consume_while(std::string_view& str, FUNC&& pred)
806 {
807 const auto start = str.begin();
808 while (!str.empty() && pred(str[0]))
809 str.remove_prefix(1);
810 return make_sv(start, str.begin());
811 }
812
815 [[nodiscard]] inline std::string_view consume_while(std::string_view& str, char c)
816 {
817 const auto start = str.begin();
818 while (str.starts_with(c))
819 str.remove_prefix(1);
820 return make_sv(start, str.begin());
821 }
822
825 template <typename... ARGS>
826 [[nodiscard]] inline std::string_view consume_while_any(std::string_view& str, ARGS&&... args)
827 {
828 const auto start = str.begin();
829 while (!str.empty() && (isany(str[0], args) || ...))
830 str.remove_prefix(1);
831 return make_sv(start, str.begin());
832 }
833
836 template <typename FUNC>
837 requires std::is_invocable_r_v<bool, FUNC, char>
838 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, FUNC&& pred)
839 {
840 const auto start = str.begin();
841 while (!str.empty() && !pred(str[0]))
842 str.remove_prefix(1);
843 return make_sv(start, str.begin());
844 }
845
848 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, char c)
849 {
850 const auto start = str.begin();
851 while (!str.empty() && str[0] != c)
852 str.remove_prefix(1);
853 return make_sv(start, str.begin());
854 }
855
858 [[nodiscard]] inline std::string_view consume_until(std::string_view& str, std::string_view end)
859 {
860 const auto it = std::ranges::search(str, end).begin();
861 const auto result = make_sv(str.begin(), it);
862 str = { it, str.end() };
863 return result;
864 }
865
866 /*
869 template <typename T>
870 requires std::constructible_from<std::string_view, T>
871 [[nodiscard]] inline std::string_view consume_until_any(std::string_view& str, T&& chars)
872 {
873 const auto charssv = std::string_view{ chars };
874 const auto start = str.begin();
875 while (!str.empty() && !charssv.contains(str[0]))
876 str.remove_prefix(1);
877 return make_sv(start, str.begin());
878 }
879 */
880
883 template <typename... ARGS>
884 [[nodiscard]] inline std::string_view consume_until_any(std::string_view& str, ARGS&&... args)
885 {
886 const auto start = str.begin();
887 while (!str.empty() && !(isany(str[0], args) || ...))
888 str.remove_prefix(1);
889 return make_sv(start, str.begin());
890 }
891
894 [[nodiscard]] inline std::string_view consume_until_delim(std::string_view& str, char c)
895 {
897
898 const auto start = str.begin();
899 while (!str.empty() && str[0] != c)
900 str.remove_prefix(1);
901 std::ignore = consume(str, c);
902 return make_sv(start, str.begin());
903 }
904
907 [[nodiscard]] inline std::string_view consume_until_delim_ex(std::string_view& str, char c)
908 {
909 const auto start = str.begin();
910 while (!str.empty() && str[0] != c)
911 str.remove_prefix(1);
912 auto result = make_sv(start, str.begin());
913 std::ignore = consume(str, c);
914 return result;
915 }
916
919 [[nodiscard]] inline std::string_view consume_n(std::string_view& str, size_t n)
920 {
921 n = std::min(str.size(), n);
922 auto result = str.substr(0, n);
923 str.remove_prefix(n);
924 return result;
925 }
926
929 template <typename FUNC>
930 requires std::is_invocable_r_v<bool, FUNC, char>
931 [[nodiscard]] inline std::string_view consume_n(std::string_view& str, size_t n, FUNC&& pred)
932 {
933 n = std::min(str.size(), n);
934 const auto start = str.begin();
935 while (n-- && !str.empty() && pred(str[0]))
936 str.remove_prefix(1);
937 return make_sv(start, str.begin());
938 }
939
955 template <typename CALLBACK_TYPE>
956 requires std::is_invocable_r_v<bool, CALLBACK_TYPE, std::string_view&>
957 [[nodiscard]] inline bool consume_delimited_list_non_empty(std::string_view& str, std::string_view delimiter, CALLBACK_TYPE callback)
958 {
959 do
960 {
961 trim_whitespace_left(str);
962 if (!callback(str))
963 return false;
964 trim_whitespace_left(str);
965 } while (consume(str, delimiter));
966 return true;
967 }
968
984 template <typename CALLBACK_TYPE>
985 requires std::is_invocable_r_v<bool, CALLBACK_TYPE, std::string_view>
986 [[nodiscard]] inline bool consume_delimited_list(std::string_view& str, std::string_view delimiter, std::string_view closer, CALLBACK_TYPE callback)
987 {
988 trim_whitespace_left(str);
989 while (!str.empty())
990 {
991 trim_whitespace_left(str);
992 if (!callback(str))
993 return false;
994 trim_whitespace_left(str);
995 if (!consume(str, delimiter))
996 return consume(str, closer);
997 }
998 return false;
999 }
1000
1002
1007
1011
1012 template <typename FUNC>
1013 requires (std::is_invocable_v<FUNC, std::string_view, bool> && !std::is_invocable_v<FUNC, std::string_view>)
1014 constexpr auto split_invoke(FUNC& func, std::string_view sv, bool is_final) noexcept(noexcept(func(std::string_view{}, true)))
1015 {
1016 return func(sv, is_final);
1017 }
1018 template <typename FUNC>
1019 requires std::is_invocable_v<FUNC, std::string_view>
1020 constexpr auto split_invoke(FUNC& func, std::string_view sv, bool is_final) noexcept(noexcept(func(std::string_view{})))
1021 {
1022 return func(sv);
1023 }
1024
1028 template <typename FUNC>
1029 constexpr auto split(std::string_view source, char delim, FUNC&& func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
1030 {
1031 using invoke_result = decltype(split_invoke(func, std::string_view{}, false));
1032
1033 size_t next = 0;
1034 while ((next = source.find_first_of(delim)) != std::string::npos)
1035 {
1036 if constexpr (std::is_constructible_v<bool, invoke_result>)
1037 {
1038 if (auto r = split_invoke(func, source.substr(0, next), false); !r)
1039 return r;
1040 }
1041 else
1042 split_invoke(func, source.substr(0, next), false);
1043
1044 source.remove_prefix(next + 1);
1045 }
1046 return split_invoke(func, source, true);
1047 }
1048
1052 template <typename FUNC>
1053 constexpr void split(std::string_view source, std::string_view delim, FUNC&& func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
1054 {
1055 const size_t delim_size = delim.size();
1056 if (delim_size == 0) return;
1057
1058 size_t next = 0;
1059 while ((next = source.find(delim)) != std::string::npos)
1060 {
1061 split_invoke(func, source.substr(0, next), false);
1062 source.remove_prefix(next + delim_size);
1063 }
1064 split_invoke(func, source, true);
1065 }
1066
1070 template <typename FUNC>
1071 constexpr void split_on_any(std::string_view source, std::string_view delim, FUNC&& func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
1072 {
1074 if (delim.empty()) return;
1075
1076 size_t next = 0;
1077 while ((next = source.find_first_of(std::string_view{ delim })) != std::string::npos)
1078 {
1079 split_invoke(func, source.substr(0, next), false);
1080 source.remove_prefix(next + 1);
1081 }
1082 split_invoke(func, source, true);
1083 }
1084
1099 template <typename DELIM_FUNC, typename FUNC>
1100 requires std::is_invocable_r_v<size_t, DELIM_FUNC, std::string_view>
1101 inline void split_on(std::string_view source, DELIM_FUNC&& delim, FUNC&& func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)) && noexcept(delim(std::string_view{})))
1102 {
1103 size_t start = 0;
1104 size_t end = 0;
1105 end = delim(source);
1106 while (end != std::string::npos)
1107 {
1108 split_invoke(func, source.substr(start, end - start), false);
1109 //source.remove_prefix(end);
1110 start = end;
1111 auto next = delim(source.substr(end + 1));
1112 if (next == std::string::npos)
1113 break;
1114 end = next + end + 1;
1115 }
1116 split_invoke(func, source.substr(start), true);
1117 }
1118
1120 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> split_at(std::string_view src, size_t split_at) noexcept
1121 {
1122 if (split_at == std::string::npos)
1123 return { src, {} };
1124 return { src.substr(0, split_at), src.substr(split_at + 1) };
1125 }
1126
1128 [[nodiscard]] constexpr bool split_at(std::string_view src, size_t split_at, std::string_view& first, std::string_view& second) noexcept
1129 {
1130 if (split_at == std::string::npos)
1131 return false;
1132 first = src.substr(0, split_at);
1133 second = src.substr(split_at + 1);
1134 return true;
1135 }
1136
1139 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> single_split(std::string_view src, char delim) noexcept
1140 {
1141 return split_at(src, src.find_first_of(delim));
1142 }
1143
1146 [[nodiscard]] constexpr std::pair<std::string_view, std::string_view> single_split_last(std::string_view src, char delim) noexcept
1147 {
1148 return split_at(src, src.find_last_of(delim));
1149 }
1150
1155 [[nodiscard]] constexpr bool single_split(std::string_view src, char delim, std::string_view& first, std::string_view& second) noexcept
1156 {
1157 return split_at(src, src.find_first_of(delim), first, second);
1158 }
1159
1164 [[nodiscard]] constexpr bool single_split_last(std::string_view src, char delim, std::string_view& first, std::string_view& second) noexcept
1165 {
1166 return split_at(src, src.find_last_of(delim), first, second);
1167 }
1168
1180 template <typename FUNC>
1181 inline void natural_split(std::string_view source, char delim, FUNC&& func) noexcept(noexcept(split_invoke(func, source, delim)))
1182 {
1183 size_t next = 0;
1184 while ((next = source.find_first_of(delim)) != std::string::npos)
1185 {
1186 split_invoke(func, source.substr(0, next), false);
1187 source.remove_prefix(next + 1);
1188
1189 if ((next = source.find_first_not_of(delim)) == std::string::npos)
1190 return;
1191
1192 source.remove_prefix(next);
1193 }
1194
1195 if (!source.empty())
1196 split_invoke(func, source, true);
1197 }
1198
1202 template <typename RESULT_TYPE = std::string_view, string_or_char DELIM>
1203 [[nodiscard]] constexpr std::vector<RESULT_TYPE> split(std::string_view source, DELIM&& delim) noexcept
1204 {
1205 std::vector<RESULT_TYPE> result;
1206 ::ghassanpl::string_ops::split(source, std::forward<DELIM>(delim), [&result](std::string_view str) {
1207 result.push_back(RESULT_TYPE{ str });
1208 });
1209 return result;
1210 }
1211
1215 template <typename RESULT_TYPE = std::string_view>
1216 [[nodiscard]] constexpr std::vector<RESULT_TYPE> split_on_any(std::string_view source, std::string_view delim) noexcept
1217 {
1219 std::vector<RESULT_TYPE> result;
1220 ::ghassanpl::string_ops::split_on_any(source, delim, [&result](std::string_view str) {
1221 result.push_back(RESULT_TYPE{ str });
1222 });
1223 return result;
1224 }
1225
1226
1230 template <typename RESULT_TYPE = std::string_view, typename DELIM_FUNC>
1231 requires std::is_invocable_r_v<size_t, DELIM_FUNC, std::string_view>
1232 [[nodiscard]] std::vector<RESULT_TYPE> split_on(std::string_view source, DELIM_FUNC&& delim) noexcept(noexcept(delim(std::string_view{})))
1233 {
1234 std::vector<RESULT_TYPE> result;
1235 ::ghassanpl::string_ops::split_on(source, std::forward<DELIM_FUNC>(delim), [&result](std::string_view str) {
1236 result.push_back(RESULT_TYPE{ str });
1237 });
1238 return result;
1239 }
1240
1243 template <typename RESULT_TYPE = std::string_view, string_or_char DELIM>
1244 [[nodiscard]] inline std::vector<RESULT_TYPE> natural_split(std::string_view source, DELIM&& delim) noexcept
1245 {
1246 std::vector<RESULT_TYPE> result;
1247 ::ghassanpl::string_ops::natural_split(source, std::forward<DELIM>(delim), [&result](std::string_view str) {
1248 result.push_back(RESULT_TYPE{ str });
1249 });
1250 return result;
1251 }
1252
1254
1260
1262 template <typename T>
1263 [[nodiscard]] inline auto join(T&& source)
1264 {
1265 std::stringstream strm;
1266 for (auto&& p : std::forward<T>(source))
1267 strm << p;
1268 return strm.str();
1269 }
1270
1272 template <typename T, string_or_char DELIM>
1273 [[nodiscard]] inline auto join(T&& source, DELIM const& delim)
1274 {
1275 std::stringstream strm;
1276 bool first = true;
1277 for (auto&& p : std::forward<T>(source))
1278 {
1279 if (!first) strm << delim;
1280 strm << p;
1281 first = false;
1282 }
1283 return strm.str();
1284 }
1285
1287 template <typename... RANGES, string_or_char DELIM>
1288 [[nodiscard]] inline auto join_multiple(DELIM const& delim, RANGES&&... sources)
1289 {
1290 std::stringstream strm;
1291 bool first = true;
1292 ([&]<typename RANGE>(RANGE && source) {
1293 for (auto&& p : std::forward<RANGE>(source))
1294 {
1295 if (!first) strm << delim;
1296 strm << p;
1297 first = false;
1298 }
1299 }(std::forward<RANGES>(sources)), ...);
1300 return strm.str();
1301 }
1302
1303
1306 template <typename T, string_or_char DELIM, string_or_char LAST_DELIM>
1307 [[nodiscard]] inline auto join_and(T&& source, DELIM const& delim, LAST_DELIM&& last_delim)
1308 {
1309 using std::ranges::begin;
1310 using std::ranges::end;
1311 using std::next;
1312
1313 std::stringstream strm;
1314 bool first = true;
1315
1316 auto&& endit = end(source);
1317 for (auto it = begin(source); it != endit; ++it)
1318 {
1319 if (!first)
1320 {
1321 if (next(it) == endit)
1322 strm << std::forward<LAST_DELIM>(last_delim);
1323 else
1324 strm << delim;
1325 }
1326 strm << *it;
1327 first = false;
1328 }
1329 return strm.str();
1330 }
1331
1332
1336 template <typename T, string_or_char DELIM, string_or_char LAST_DELIM, typename FUNC>
1337 [[nodiscard]] inline auto join_and(T&& source, DELIM const& delim, LAST_DELIM&& last_delim, FUNC&& transform_func)
1338 {
1339 using std::ranges::begin;
1340 using std::ranges::end;
1341 using std::next;
1342
1343 std::stringstream strm;
1344 bool first = true;
1345
1346 auto&& endit = end(source);
1347 for (auto it = begin(source); it != endit; ++it)
1348 {
1349 if (!first)
1350 {
1351 if (next(it) == endit)
1352 strm << std::forward<LAST_DELIM>(last_delim);
1353 else
1354 strm << delim;
1355 }
1356 strm << transform_func(*it);
1357 first = false;
1358 }
1359 return strm.str();
1360 }
1361
1365 template <typename T, typename FUNC, string_or_char DELIM>
1366 [[nodiscard]] inline auto join(T&& source, DELIM const& delim, FUNC&& transform_func)
1367 {
1368 std::stringstream strm;
1369 bool first = true;
1370 for (auto&& p : source)
1371 {
1372 if (!first) strm << delim;
1373 strm << transform_func(p);
1374 first = false;
1375 }
1376 return strm.str();
1377 }
1378
1380
1381 template <string_or_char NEEDLE, typename FUNC>
1382 inline void find_all(std::string_view subject, NEEDLE&& search, FUNC&& func)
1383 {
1384 const auto search_sv = make_sv(search);
1385
1386 if (search_sv.empty())
1387 return;
1388
1389 size_t pos = 0;
1390 while ((pos = subject.find(search, pos)) != std::string::npos)
1391 {
1392 func(subject.substr(pos, search_sv.size()));
1393 pos += search_sv.size();
1394 }
1395 }
1396
1397 template <typename RESULT_TYPE = std::string_view, string_or_char NEEDLE>
1398 inline std::vector<RESULT_TYPE> find_all(std::string_view subject, NEEDLE&& search)
1399 {
1400 std::vector<RESULT_TYPE> result;
1401 find_all(subject, std::forward<NEEDLE>(search), [&](std::string_view sv) { result.push_back(RESULT_TYPE{ sv }); });
1402 return result;
1403 }
1404
1410
1411 template <string_or_char NEEDLE, string_or_char REPLACE>
1412 inline void replace(std::string& subject, NEEDLE&& search, REPLACE&& replace)
1413 {
1414 using std::empty;
1415 using std::size;
1416
1417 const auto search_sv = make_sv(search);
1418 const auto replace_sv = make_sv(replace);
1419
1420 if (search_sv.empty())
1421 return;
1422
1423 size_t pos = 0;
1424 while ((pos = subject.find(search, pos)) != std::string::npos)
1425 {
1426 subject.replace(pos, search_sv.size(), replace_sv);
1427 pos += replace_sv.size();
1428 }
1429 }
1430
1431 template <string_or_char NEEDLE, string_or_char REPLACE>
1432 [[nodiscard]] inline std::string replaced(std::string subject, NEEDLE&& search, REPLACE&& replace)
1433 {
1434 string_ops::replace(subject, std::forward<NEEDLE>(search), std::forward<REPLACE>(replace));
1435 return subject;
1436 }
1437
1438 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1439 inline void quote(std::string& subject, DELIMITER delimiter = '"', ESCAPE escape = '\\')
1440 {
1441 const char replace_delim[]{ escape, delimiter, '\0' };
1442 const char replace_escape[]{ escape, escape, '\0' };
1443 if constexpr (requires { delimiter != escape; })
1444 {
1445 if (delimiter != escape)
1446 ::ghassanpl::string_ops::replace(subject, escape, replace_escape);
1447 }
1448 else
1449 ::ghassanpl::string_ops::replace(subject, escape, replace_escape);
1450 ::ghassanpl::string_ops::replace(subject, delimiter, replace_delim);
1451 subject.insert(subject.begin(), delimiter);
1452 subject += delimiter;
1453 }
1454
1455 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1456 [[nodiscard]] inline std::string quoted(std::string&& subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1457 {
1458 ::ghassanpl::string_ops::quote(subject, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1459 return subject;
1460 }
1461
1462 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1463 [[nodiscard]] inline std::string quoted(std::string_view subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1464 {
1465 auto result = std::string{ subject };
1466 ::ghassanpl::string_ops::quote(result, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1467 return result;
1468 }
1469
1470 template <string_or_char DELIMITER = char, string_or_char ESCAPE = char>
1471 [[nodiscard]] inline std::string quoted(const char* subject, DELIMITER&& delimiter = '"', ESCAPE&& escape = '\\')
1472 {
1473 return ::ghassanpl::string_ops::quoted(std::string{ subject }, std::forward<DELIMITER>(delimiter), std::forward<ESCAPE>(escape));
1474 }
1475
1476 template <typename ESCAPE_FUNC>
1477 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>>
1478 inline void escape(std::string& subject, std::string_view chars_to_escape, ESCAPE_FUNC&& escape_func)
1479 {
1480 if (chars_to_escape.empty())
1481 return;
1482
1483 size_t pos = 0;
1484 while ((pos = subject.find_first_of(chars_to_escape, pos)) != std::string::npos)
1485 {
1486 auto escape_str = escape_func(subject.substr(pos, 1));
1487 subject.replace(pos, 1, escape_str);
1488 pos += escape_str.size();
1489 }
1490 }
1491
1492 template <typename ESCAPE_FUNC>
1493 requires std::is_invocable_v<ESCAPE_FUNC, char> && std::is_constructible_v<std::string_view, std::invoke_result_t<ESCAPE_FUNC, char>>
1494 inline void escape(std::string& subject, std::string_view chars_to_escape, ESCAPE_FUNC&& escape_func)
1495 {
1496 if (chars_to_escape.empty())
1497 return;
1498
1499 size_t pos = 0;
1500 while ((pos = subject.find_first_of(chars_to_escape, pos)) != std::string::npos)
1501 {
1502 auto escape_str = escape_func(subject[pos]);
1503 subject.replace(pos, 1, escape_str);
1504 pos += escape_str.size();
1505 }
1506 }
1507
1508 template <string_or_char ESCAPE = char>
1509 inline void escape(std::string& subject, std::string_view chars_to_escape, ESCAPE&& escape = '\\')
1510 {
1511 if (chars_to_escape.empty())
1512 return;
1513
1514 auto escape_str = make_sv(escape);
1515
1516 size_t pos = 0;
1517 while ((pos = subject.find_first_of(chars_to_escape, pos)) != std::string::npos)
1518 {
1519 subject.insert(pos, escape_str);
1520 pos += escape_str.size() + 1;
1521 }
1522 }
1523
1524 template <typename ESCAPE_FUNC, typename ISPRINTABLE_FUNC = decltype(ascii::isprint)>
1525 inline void escape_non_printable(std::string& subject, ESCAPE_FUNC&& escape_func, ISPRINTABLE_FUNC&& isprintable_func = ascii::isprint)
1526 {
1527 static_assert(std::is_invocable_v<ESCAPE_FUNC, char> && std::is_constructible_v<std::string_view, std::invoke_result_t<ESCAPE_FUNC, char>>,
1528 "escape function must be invocable with (char) and must return something convertible to string_view");
1529 static_assert(std::is_invocable_v<ISPRINTABLE_FUNC, char> && std::is_constructible_v<bool, std::invoke_result_t<ISPRINTABLE_FUNC, char>>,
1530 "isprintable function must be a predicate invocable with (char)");
1531
1532 size_t pos = 0;
1533 std::string::iterator it;
1534 while ((it = std::find_if_not(subject.begin() + pos, subject.end(), isprintable_func)) != subject.end())
1535 {
1536 pos = it - subject.begin();
1537 auto escape_str = escape_func(subject[pos]);
1538 subject.replace(pos, 1, escape_str);
1539 pos += escape_str.size();
1540 }
1541 }
1542
1543 inline void escape_non_printable(std::string& subject)
1544 {
1545 escape_non_printable(subject, [](char c) {
1546 return std::format("\\x{:02x}", static_cast<uint8_t>(c));
1547 });
1548 }
1549
1550 template <typename STR, string_or_char ESCAPE = char>
1551 [[nodiscard]] inline std::string escaped(STR&& subject, std::string_view to_escape = "\"\\", ESCAPE&& escape_str = '\\')
1552 {
1553 auto result = std::string{ subject };
1554 ::ghassanpl::string_ops::escape(result, to_escape, std::forward<ESCAPE>(escape_str));
1555 return result;
1556 }
1557
1558 template <typename STR>
1559 [[nodiscard]] inline std::string escaped_non_printable(STR&& subject)
1560 {
1561 auto result = std::string{ subject };
1562 ::ghassanpl::string_ops::escape_non_printable(result);
1563 return result;
1564 }
1565
1567
1569 [[nodiscard]] inline std::string url_encode(std::string_view text)
1570 {
1571 std::string result;
1572 for (auto c : text)
1573 {
1574 if (ascii::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~')
1575 {
1576 result += c;
1577 continue;
1578 }
1579
1580 result += '%';
1581 std::format_to(std::back_inserter(result), "{:02X}", (int)(unsigned char)c);
1582 }
1583 return result;
1584 }
1585
1587 [[nodiscard]] inline std::string url_unencode(std::string_view text)
1588 {
1589 std::string result;
1590 while (!text.empty())
1591 {
1592 if (text[0] == '%')
1593 {
1594 text.remove_prefix(1);
1595 if (text.size() < 2)
1596 continue;
1597 uint8_t val{};
1598 std::from_chars(text.data(), text.data() + 2, val, 16);
1599 result += (char)val;
1600 text.remove_prefix(2);
1601 continue;
1602 }
1603
1604 result += text[0];
1605 text.remove_prefix(1);
1606 }
1607 return result;
1608 }
1609
1610#if defined(__cpp_lib_format)
1611
1614 template <typename CHECKER>
1615 requires std::predicate<CHECKER, std::string_view>
1616 [[nodiscard]] inline std::string unique_name(std::string_view base_name, CHECKER&& checker)
1617 {
1618 std::string new_name = std::string{ base_name };
1619 size_t index = 1;
1620 while (!checker(std::string_view{ new_name }))
1621 new_name = std::format("{}{}", base_name, index++);
1622 return new_name;
1623 }
1624#endif
1625
1626#if !__cpp_lib_to_chars && !defined(DOXYGEN)
1627
1628#else
1630 template <std::integral T>
1631 [[nodiscard]] constexpr auto from_chars(std::string_view str, T& value, const int base = 10) noexcept {
1632 return std::from_chars(str.data(), str.data() + str.size(), value, base);
1633 }
1634
1635 template <std::floating_point T>
1636 [[nodiscard]] constexpr auto from_chars(std::string_view str, T& value, const std::chars_format chars_format = std::chars_format::general) noexcept {
1637 return std::from_chars(str.data(), str.data() + str.size(), value, chars_format);
1638 }
1639#endif
1640
1647 template <bool SINGLE>
1648 struct split_range
1649 {
1650 split_range(std::string_view source, std::string_view split_on) : mSource(source), mSplit(split_on) {}
1651
1652 struct split_range_iterator
1653 {
1654 const char* RangeStart;
1655 const char* RangeEnd;
1656 const char* SourceEnd;
1657 std::string_view SplitChars;
1658
1659 bool operator!=(const split_range_iterator& other) const { return RangeStart != other.SourceEnd; }
1660 split_range_iterator& operator++(int) {
1661 auto copy = *this;
1662 ++*this;
1663 return copy;
1664 }
1665
1666 split_range_iterator operator++() {
1667 auto rs = RangeEnd;
1668 auto se = SourceEnd;
1669 auto sc = SplitChars;
1670
1671 if (SINGLE)
1672 {
1673 if (rs < se && string_contains(sc, *rs))
1674 ++rs;
1675 }
1676 else
1677 {
1678 while (rs < se && string_contains(sc, *rs))
1679 ++rs;
1680 }
1681
1682 RangeStart = rs;
1683
1684 while (rs < se && !string_contains(sc, *rs))
1685 ++rs;
1686
1687 RangeEnd = rs;
1688 return *this;
1689 }
1690
1691 std::pair<const char*, const char*> operator*() const { return { RangeStart, RangeEnd }; }
1692 };
1693
1694 split_range_iterator begin() {
1695 split_range_iterator it = { nullptr, mSource.data(), mSource.data() + mSource.size(), mSplit };
1696 ++it;
1697 return it;
1698 }
1699
1700 split_range_iterator end() {
1701 auto se = mSource.data() + mSource.size();
1702 return { se, se, se, mSplit };
1703 }
1704
1706 auto source() const { return mSource; }
1708 auto split_on() const { return mSplit; }
1709
1710 private:
1711
1712 std::string_view mSource;
1713 std::string_view mSplit;
1714 };
1715
1716 // static_assert(std::ranges::range<split_range<true>>);
1717
1726 template <typename RESULT_TYPE = std::string_view, typename T, typename FUNC>
1727 requires std::is_arithmetic_v<T>&& std::is_invocable_r_v<T, FUNC, std::string_view>
1728 std::vector<RESULT_TYPE> word_wrap(std::string_view _source, T max_width, FUNC width_getter, std::string_view split_chars = " ")
1729 {
1730 std::vector<RESULT_TYPE> result;
1731
1732 auto space_width = width_getter(" ");
1733
1734 for (auto line : split_range<true>(_source, "\n"))
1735 {
1736 auto space_left = max_width;
1737 auto line_start = line.first;
1738
1739 for (auto r : split_range<false>({ line.first, size_t(line.second - line.first) }, split_chars))
1740 {
1741 const auto word_width = width_getter({ r.first, size_t(r.second - r.first) });
1742 const auto width = word_width + width_getter(std::string{ *r.second });
1743 if (width > space_left)
1744 {
1745 result.push_back(RESULT_TYPE{ line_start, size_t(r.first - line_start) });
1746 space_left = max_width - word_width;
1747 line_start = r.first;
1748 }
1749 else
1750 {
1751 space_left -= width;
1752 }
1753 }
1754
1755 result.push_back(RESULT_TYPE{ line_start, size_t(line.second - line_start) });
1756 }
1757
1758 return result;
1759 }
1760
1762
1765 template <typename RESULT_TYPE = std::string_view, typename T>
1766 requires std::is_arithmetic_v<T>
1767 std::vector<RESULT_TYPE> word_wrap(std::string_view _source, T max_width, T letter_width)
1768 {
1769 return ::ghassanpl::string_ops::word_wrap<RESULT_TYPE>(_source, max_width, [letter_width](std::string_view str) { return T(str.size() * letter_width); });
1770 }
1771
1772 auto repeat_string(stringable auto str, size_t num)
1773 {
1774 const auto sv = make_sv(str);
1775 std::basic_string<typename decltype(sv)::value_type> result;
1776 result.reserve(num * sv.size());
1777 for (size_t i = 0; i < num; ++i)
1778 result.append(sv);
1779 return result;
1780 }
1781
1782 inline size_t levenshtein_distance(std::string_view s1, std::string_view s2)
1783 {
1784 if (s1.size() > s2.size())
1785 std::swap(s1, s2);
1786
1787 const auto min_size = s1.size();
1788 const auto max_size = s2.size();
1789
1790 std::vector<size_t> lev_dist(min_size + 1);
1791 for (size_t i = 0; i < lev_dist.size(); ++i)
1792 lev_dist[i] = i;
1793
1794 for (size_t j = 1; j <= max_size; ++j)
1795 {
1796 size_t previous_diagonal = lev_dist[0];
1797 size_t previous_diagonal_save{};
1798 ++lev_dist[0];
1799
1800 for (size_t i = 1; i <= min_size; ++i)
1801 {
1802 previous_diagonal_save = lev_dist[i];
1803 if (s1[i - 1] == s2[j - 1])
1804 lev_dist[i] = previous_diagonal;
1805 else
1806 lev_dist[i] = std::min({ lev_dist[i - 1], lev_dist[i], previous_diagonal }) + 1;
1807 previous_diagonal = previous_diagonal_save;
1808 }
1809 }
1810
1811 return lev_dist[min_size];
1812 }
1813
1814#ifndef __clang__
1815 namespace detail
1816 {
1817 template <std::integral T>
1818 [[nodiscard]] inline auto string_to_number(std::string_view str, size_t* idx = nullptr, int base = 10)
1819 {
1820 const auto begin = str.data();
1821 auto end = str.data() + str.size();
1822 T value{};
1823 if (auto res = std::from_chars(begin, end, value, base); idx && res.ec == std::error_code{})
1824 *idx = size_t(res.ptr - begin);
1825 return value;
1826 }
1827
1828 template <std::floating_point T>
1829 [[nodiscard]] inline auto string_to_number(std::string_view str, size_t* idx = nullptr, std::chars_format format = std::chars_format::general)
1830 {
1831 const auto begin = str.data();
1832 auto end = str.data() + str.size();
1833 T value{};
1834 if (auto res = std::from_chars(begin, end, value, format); idx && res.ec == std::error_code{})
1835 *idx = size_t(res.ptr - begin);
1836 return value;
1837 }
1838 }
1839
1843 [[nodiscard]] inline auto stob(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<uint8_t>(str, idx, base); }
1844 [[nodiscard]] inline auto stoc(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<char>(str, idx, base); }
1845 [[nodiscard]] inline auto stouc(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<unsigned char>(str, idx, base); }
1846
1847 [[nodiscard]] inline auto stoi(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<int>(str, idx, base); }
1848 [[nodiscard]] inline auto stol(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<long>(str, idx, base); }
1849 [[nodiscard]] inline auto stoll(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<long long>(str, idx, base); }
1850 [[nodiscard]] inline auto stoul(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<unsigned long>(str, idx, base); }
1851 [[nodiscard]] inline auto stoull(std::string_view str, size_t* idx = nullptr, int base = 10) { return detail::string_to_number<unsigned long long>(str, idx, base); }
1852 [[nodiscard]] inline auto 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); }
1853 [[nodiscard]] inline auto 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); }
1854 [[nodiscard]] inline auto 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); }
1856
1858 template <std::integral T = std::make_unsigned_t<size_t>>
1859 [[nodiscard]] inline auto to_number(std::string_view str, int base = 10) -> expected<T, std::from_chars_result> {
1860 const auto begin = str.data();
1861 auto end = str.data() + str.size();
1862 T value{};
1863 if (auto res = std::from_chars(begin, end, value, base); res.ec != std::error_code{} || res.ptr != end)
1864 return unexpected(res);
1865 return value;
1866 }
1867
1869 template <std::floating_point T = std::float_t>
1870 [[nodiscard]] inline auto to_number(std::string_view str, std::chars_format format = std::chars_format::general) -> expected<T, std::from_chars_result> {
1871 const auto begin = str.data();
1872 auto end = str.data() + str.size();
1873 T value{};
1874 if (auto res = std::from_chars(begin, end, value, format); res.ec != std::error_code{} || res.ptr != end)
1875 return unexpected(res);
1876 return value;
1877 }
1878
1879 template <typename CALLBACK_TYPE>
1880 requires std::invocable<CALLBACK_TYPE, size_t, std::string_view, std::string&>
1881 std::string callback_format(std::string_view fmt, CALLBACK_TYPE&& callback)
1882 {
1884 std::string result;
1885 size_t aid = 0;
1886 while (!fmt.empty())
1887 {
1888 auto text = consume_until(fmt, '{');
1889 result += text;
1890 if (fmt.empty())
1891 continue;
1892 std::ignore = consume(fmt, '{');
1893
1894 if (consume(fmt, '{'))
1895 {
1896 result += '{';
1897 continue;
1898 }
1899 else
1900 {
1901 std::string fmt_clause_str = "{";
1902 auto fmt_clause = string_ops::consume_until_delim(fmt, '}');
1903 if (!string_ops::consume_at_end(fmt_clause, '}'))
1904 throw std::format_error("missing '}' in format string");
1905 if (!fmt_clause.empty())
1906 {
1907 auto num = string_ops::consume_until(fmt_clause, ':');
1908 if (!num.empty())
1909 aid = string_ops::stoull(num);
1910 fmt_clause_str += fmt_clause;
1911 }
1912 fmt_clause_str += '}';
1913 callback(aid, std::string_view{ fmt_clause_str }, result);
1914 ++aid;
1915 }
1916 }
1917 return result;
1918 }
1919
1920#endif
1921
1923#define GHPL_FORMAT_TEMPLATE typename... GHPL_ARGS
1925#define GHPL_FORMAT_ARGS std::format_string<GHPL_ARGS...> ghpl_fmt, GHPL_ARGS&&... ghpl_args
1927#define GHPL_FORMAT_FORWARD ghpl_fmt, ghpl_args...
1929#define GHPL_FORMAT_CALL std::format(ghpl_fmt, std::forward<GHPL_ARGS>(ghpl_args)...)
1931#define GHPL_PRINT_CALL std::print_unicode(ghpl_fmt, std::forward<GHPL_ARGS>(ghpl_args)...)
1932
1934}
1935
Whether the type is a native char type.
Definition string_ops.h:100
Can a type be bit-cast to a native/utf char type?
Definition string_ops.h:90
The type is a string with a 16-bit char type.
Definition string_ops.h:61
The type is a string with an 32-bit char type.
Definition string_ops.h:71
The type is a string with an 8-bit char type.
Definition string_ops.h:51
The type is a stringable or a character.
Definition string_ops.h:42
The type is a string view with a 16-bit char type.
Definition string_ops.h:67
The type is a string view with a 32-bit char type.
Definition string_ops.h:77
The type is a string view with an 8-bit char type.
Definition string_ops.h:57
The type is convertible to a string view with a 16-bit char type.
Definition string_ops.h:64
The type is convertible to a string view with a 32-bit char type.
Definition string_ops.h:74
The type is convertible to a string view with an 8-bit char type.
Definition string_ops.h:54
Whether the type is a native or utf char type.
Definition string_ops.h:108
The type is "stringable", that is, a continuous range of characters.
Definition string_ops.h:47
Whether the type is a utf char type.
Definition string_ops.h:104
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:474
constexpr auto hex_digit_chars
All characters that match ascii::isxdigit.
Definition string_ops.h:395
constexpr auto blank_chars
All characters that match ascii::isblank.
Definition string_ops.h:404
constexpr auto control_chars
All characters that match ascii::iscntrl.
Definition string_ops.h:403
constexpr auto punctuation_chars
All characters that match ascii::ispunct.
Definition string_ops.h:400
constexpr auto lowercase_chars
All characters that match ascii::islower.
Definition string_ops.h:401
constexpr auto alphanumeric_chars
All characters that match ascii::isalnum.
Definition string_ops.h:396
constexpr auto whitespace_chars
All characters that match ascii::isspace.
Definition string_ops.h:399
constexpr auto printable_chars
All characters that match ascii::isprint.
Definition string_ops.h:406
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:479
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:476
constexpr auto digit_chars
All characters that match ascii::isdigit.
Definition string_ops.h:393
constexpr auto identifier_start_chars
All characters that match ascii::isidentstart.
Definition string_ops.h:398
constexpr auto uppercase_chars
All characters that match ascii::isupper.
Definition string_ops.h:402
constexpr auto alphabetic_chars
All characters that match ascii::isalpha.
Definition string_ops.h:392
constexpr auto octal_digit_chars
All characters that match ascii::isodigit.
Definition string_ops.h:394
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:411
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:482
constexpr auto graphical_chars
All characters that match ascii::isgraph.
Definition string_ops.h:405
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:467
constexpr auto identifier_chars
All characters that match ascii::isident.
Definition string_ops.h:397
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:79
constexpr CONTAINER to(RANGE &&range, TYPES &&... args)
to<container>();
Definition ranges.h:386
constexpr bool isascii(char32_t cp) noexcept
Returns true if cp is an ascii codepoint.
Definition string_ops.h:572
constexpr bool string_contains(std::string_view str, char c)
A pre-C++23 version of str.contains(c).
Definition string_ops.h:579
void natural_split(std::string_view source, char delim, FUNC &&func) noexcept(noexcept(split_invoke(func, source, delim)))
Performs a more natural split of the string, that is: ignoring multiple delimiters in a row,...
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:80
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:765
std::vector< RESULT_TYPE > word_wrap(std::string_view _source, T max_width, FUNC width_getter, std::string_view split_chars=" ")
Performs a basic word-wrapping split of _source, as if it was constrained to max_width.
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:120
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:838
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:615
constexpr bool isany(char32_t cp, T &&chars) noexcept
Checks if cp is any of the characters in chars.
Definition string_ops.h:688
auto to_number(std::string_view str, int base=10) -> expected< T, std::from_chars_result >
If str contains an integral number (and nothing else), returns that number, else an error-carrying fr...
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:738
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.
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:269
bool consume_delimited_list(std::string_view &str, std::string_view delimiter, std::string_view closer, CALLBACK_TYPE callback)
Consumes a list of delimiter-delimited strings, ended with closer, calling callback(str) each time; w...
Definition string_ops.h:986
constexpr void split_on_any(std::string_view source, std::string_view delim, FUNC &&func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
Performs a basic "split" operation, calling func for each part of source delimited by any character i...
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:207
std::string_view consume_until_delim_ex(std::string_view &str, char c)
Consumes characters from the beginning of str until one is equal to c, consuming c but not including ...
Definition string_ops.h:907
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.
bool consume_delimited_list_non_empty(std::string_view &str, std::string_view delimiter, CALLBACK_TYPE callback)
Consumes a list of delimiter-delimited strings, calling callback(str) each time; whitespaces before a...
Definition string_ops.h:957
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:826
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:702
void remove_prefix(std::string &str, size_t prefix_size) noexcept
Removes the first prefix_size characters from str.
Definition string_ops.h:647
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:632
constexpr void trim_whitespace_right(std::string_view &str) noexcept
TODO: trim_*(std::string&) overloads.
Definition string_ops.h:674
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:83
void remove_suffix(std::string &str, size_t prefix_size) noexcept
Removes the last prefix_size characters from str.
Definition string_ops.h:640
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:884
constexpr auto split(std::string_view source, char delim, FUNC &&func) noexcept(noexcept(split_invoke(func, std::string_view{}, true)))
Performs a basic "split" operation, calling func for each part of source delimited by delim.
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 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 consume_until_delim(std::string_view &str, char c)
Consumes characters from the beginning of str until one is equal to c, consuming c and including it a...
Definition string_ops.h:894
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.
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:610
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:625
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:805
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:576
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:244
bool consume_at_end(std::string_view &str, char val)
Consumes the last character from str if it matches val.
Definition string_ops.h:777
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:593
void split_on(std::string_view source, DELIM_FUNC &&delim, FUNC &&func) noexcept(noexcept(split_invoke(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...
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:112
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:620
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:605
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:919
std::string to_string(T floating_point)
float overload
Contains functions that return predicates; appropriate for functions like std::all_of.
Definition functional.h:82
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 characters we're splitting on.