15#if !defined(__cpp_concepts)
16#error "This library requires concepts"
18#if !defined(__cpp_lib_ranges)
19#error "This library requires ranges"
21#if defined(__cpp_lib_format)
24#if !defined(__cpp_lib_to_address)
25#error "This library requires std::to_address"
29static_assert(CHAR_BIT >= 8);
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>;
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>);
49 concept string8 = std::same_as<T, std::string> || std::same_as<T, std::u8string>;
52 concept stringable8 = std::convertible_to<T, std::string_view> || std::convertible_to<T, std::u8string_view>;
55 concept string_view8 = std::same_as<T, std::string_view> || std::same_as<T, std::u8string_view>;
59 concept string16 = (
sizeof(
wchar_t) ==
sizeof(
char16_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u16string>;
62 concept stringable16 = (
sizeof(
wchar_t) ==
sizeof(
char16_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u16string_view>;
65 concept string_view16 = (
sizeof(
wchar_t) ==
sizeof(
char16_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u16string_view>;
69 concept string32 = (
sizeof(
wchar_t) ==
sizeof(
char32_t) && std::same_as<T, std::wstring>) || std::same_as<T, std::u32string>;
72 concept stringable32 = (
sizeof(
wchar_t) ==
sizeof(
char32_t) && std::convertible_to<T, std::wstring_view>) || std::convertible_to<T, std::u32string_view>;
75 concept string_view32 = (
sizeof(
wchar_t) ==
sizeof(
char32_t) && std::same_as<T, std::wstring_view>) || std::same_as<T, std::u32string_view>;
83 template <
typename A,
typename B>
84 concept same_size_and_alignment =
sizeof(
A) ==
sizeof(
B) &&
alignof(
A) ==
alignof(
B);
88 concept charable = (std::is_trivially_copyable_v<T> && (
98 concept char_type = std::same_as<T, char> || std::same_as<T, wchar_t>;
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>;
105 template <
typename T>
109 template <
charable 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,
117 template <
charable T>
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,
124 template <
charable T>
125 using best_stringable_type = std::conditional_t<stringable_base_type<T>,
141 template <
typename C =
char>
142 [[
nodiscard]]
constexpr std::basic_string_view<C> make_sv(std::nullptr_t, std::nullptr_t)
noexcept {
return {}; }
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))
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))) {
160 template <
typename T>
163 static_assert(!std::is_rvalue_reference_v<
decltype(
single_char)>,
"cannot create string_view's from single char rvalues");
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>{};
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;
180 template <std::ranges::range RANGE>
184 return make_sv(std::ranges::begin(
range), std::ranges::end(
range));
190 using char_type =
typename decltype(
sv)::value_type;
191 return std::basic_string<char_type>{
sv };
203 template <
typename COUT,
typename CIN>
207 return {
reinterpret_cast<COUT const*
>(
id.data()),
id.size() };
215 [[
nodiscard]]
inline std::string to_string(std::string_view from)
noexcept {
return std::string{ from }; }
217 [[
nodiscard]]
inline std::string to_string(std::u8string_view from)
noexcept {
return std::string{ from.data(), from.data() + from.size() }; }
220 [[
nodiscard]]
inline std::string to_string(T
const&
t)
requires requires { std::to_string(
t); } {
return std::to_string(
t); }
222 [[
nodiscard]]
constexpr std::string
const& to_string(std::same_as<std::string>
auto const&
s) {
return s; }
225 [[
nodiscard]]
inline std::string to_string(std::optional<T>
const&
o) {
if (
o.has_value())
return std::to_string(
o.value());
return "(empty)"; }
288 const auto flag_el = (cp >> 6) & 1;
293 template <u
int64_t HIGH, u
int64_t LOW>
303 constexpr std::array<uint64_t, 2>
get_flags_for(std::string_view str)
noexcept
305 std::array<uint64_t, 2> result{ 0,0 };
311 template <
typename FUNC>
314 std::array<uint64_t, 2> result{ 0,0 };
315 for (
char32_t c = 0; c < 128; ++c)
317 result[(
char32_t(c) >> 6) & 1] |= (
char32_t(c) & 63);
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; }
342 [[
nodiscard]]
constexpr char32_t toupper(
char32_t cp)
noexcept {
343 return (cp >= 97 && cp <= 122) ? (cp ^ 0b100000) : cp;
345 [[
nodiscard]]
constexpr char32_t tolower(
char32_t cp)
noexcept {
346 return (cp >= 65 && cp <= 90) ? (cp | 0b100000) : cp;
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);
358 template <
typename T, auto FUNC>
359 constexpr auto compute_characters_matching() {
361 std::array<T, char_count> result{};
362 std::ranges::copy_if(std::views::iota(T{ 1 }, T{ 127 }), result.begin(),
FUNC);
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>();
388 template <
stringable T>
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);
395 template <
stringable T>
396 [[
nodiscard]]
constexpr std::string tolower(T
const& str)
noexcept {
397 using std::ranges::begin;
398 using std::ranges::end;
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); });
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); });
413 [[
nodiscard]]
constexpr std::string tolower(
const char* str)
noexcept {
415 return tolower(std::string{ str });
420 template <
stringable T>
421 [[
nodiscard]]
inline std::string toupper(T
const& str)
noexcept {
422 using std::ranges::begin;
423 using std::ranges::end;
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); });
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); });
438 [[
nodiscard]]
inline std::string toupper(
const char* str)
noexcept {
440 return toupper(std::string{ str });
446 if (str.empty())
return str;
447 str[0] = (
char)::ghassanpl::string_ops::ascii::toupper(str[0]);
464 [[
nodiscard]]
constexpr bool strings_equal_ignore_case(std::string_view
sa, std::string_view
sb)
466 return std::ranges::equal(
sa,
sb, [](
char a,
char b) { return ::ghassanpl::string_ops::ascii::toupper(a) == ::ghassanpl::string_ops::ascii::toupper(b); });
469 [[
nodiscard]]
constexpr bool string_starts_with_ignore_case(std::string_view a, std::string_view b)
471 return strings_equal_ignore_case(a.substr(0, b.size()), b);
474 [[
nodiscard]]
constexpr bool string_ends_with_ignore_case(std::string_view a, std::string_view b)
476 if (b.size() > a.size())
return false;
477 return strings_equal_ignore_case(a.substr(a.size() - b.size()), b);
482 return std::ranges::search(
485 [](
char ch1,
char ch2) { return ::ghassanpl::string_ops::ascii::tolower(
ch1) == ::ghassanpl::string_ops::ascii::tolower(
ch2); }
489 [[
nodiscard]]
constexpr auto string_find_last_ignore_case(std::string_view a, std::string_view b)
491 return std::ranges::find_end(
494 [](
char ch1,
char ch2) { return ::ghassanpl::string_ops::ascii::tolower(
ch1) == ::ghassanpl::string_ops::ascii::tolower(
ch2); }
498 [[
nodiscard]]
constexpr auto string_contains_ignore_case(std::string_view a, std::string_view b)
500 return string_find_ignore_case(a, b) != a.end();
503 [[
nodiscard]]
constexpr bool lexicographical_compare_ignore_case(std::string_view
first, std::string_view
second)
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); });
509 [[
nodiscard]]
constexpr auto lexicographical_compare_ignore_case_three_way(std::string_view a, std::string_view b)
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); });
517 struct string_view_case_insensitive
519 std::string_view value;
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); }
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); }
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} }; }
537#pragma push_macro("isascii")
541#pragma pop_macro("isascii")
549#if __cpp_lib_string_contains
550 return str.contains(c);
552 return str.find(c) != std::string_view::npos;
563 start = str.size() + start;
564 if (start < 0) start = 0;
566 if (start >=
static_cast<intptr_t>(str.size()))
return {};
567 return str.substr(start,
static_cast<size_t>(count));
571 [[
nodiscard]]
inline std::string_view
prefix(std::string_view str,
size_t count)
noexcept {
572 return str.substr(0, count);
577 return str.substr(0, str.size() - std::min(str.size(), count));
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()));
587 return str.substr(std::min(str.size(), count));
592 str.erase(std::min(start + count, str.size()));
593 str.erase(0, std::min(start, str.size()));
599 const auto from_ = std::min(from,
to);
600 const auto to_ = std::max(from,
to);
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()); }
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; }
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()); }
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)); }
636 template <std::ranges::random_access_range T>
639 using char_type = std::ranges::range_value_t<T>;
643 constexpr bool isany(
char32_t c,
char32_t c2)
noexcept {
return c ==
c2; }
656 const auto result = str[0];
657 str.remove_prefix(1);
665 if (str.starts_with(
val))
667 str.remove_prefix(1);
677 if (str.starts_with(
val))
679 str.remove_prefix(
val.size());
687 template <
typename...
ARGS>
690 if (!str.empty() && (
isany(str[0],
args) || ...))
692 const auto result = str[0];
693 str.remove_prefix(1);
701 template <
typename PRED>
702 requires std::is_invocable_r_v<bool, PRED, char>
705 if (!str.empty() &&
pred(str[0]))
707 const auto result = str[0];
708 str.remove_prefix(1);
719 const auto result = str[0];
720 str.remove_prefix(1);
729 if (str.ends_with(
val))
731 str.remove_suffix(1);
743 if (str.ends_with(
val))
745 str.remove_suffix(
val.size());
753 template <
typename FUNC>
754 requires std::is_invocable_r_v<bool, FUNC, char>
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());
767 const auto start = str.begin();
768 while (str.starts_with(c))
769 str.remove_prefix(1);
770 return make_sv(start, str.begin());
775 template <
typename...
ARGS>
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());
786 template <
typename FUNC>
787 requires std::is_invocable_r_v<bool, FUNC, char>
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());
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());
810 auto it = std::ranges::search(str, end).begin();
811 auto result = make_sv(str.begin(),
it);
812 str = {
it, str.end() };
833 template <
typename...
ARGS>
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());
848 const auto start = str.begin();
849 while (!str.empty() && str[0] != c)
850 str.remove_prefix(1);
852 return make_sv(start, str.begin());
859 n = std::min(str.size(),
n);
860 auto result = str.substr(0,
n);
861 str.remove_prefix(
n);
867 template <
typename FUNC>
868 requires std::is_invocable_r_v<bool, FUNC, char>
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());
893 template <
typename CALLBACK>
894 requires std::is_invocable_r_v<bool, CALLBACK, std::string_view&>
899 trim_whitespace_left(str);
902 trim_whitespace_left(str);
922 template <
typename CALLBACK>
923 requires std::is_invocable_r_v<bool, CALLBACK, std::string_view>
926 trim_whitespace_left(str);
929 trim_whitespace_left(str);
932 trim_whitespace_left(str);
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)))
958 while ((next = source.find_first_of(
delim)) != std::string::npos)
960 func(source.substr(0, next),
false);
961 source.remove_prefix(next + 1);
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)))
977 while ((next = source.find(
delim)) != std::string::npos)
979 func(source.substr(0, next),
false);
988 template <
typename FUNC>
989 requires std::is_invocable_v<FUNC, std::string_view, bool>
993 if (
delim.empty())
return;
996 while ((next = source.find_first_of(std::string_view{ delim })) != std::string::npos)
998 func(source.substr(0, next),
false);
999 source.remove_prefix(next + 1);
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>
1024 end =
delim(source);
1025 while (end != std::string::npos)
1027 func(source.substr(start, end - start),
false);
1030 auto next =
delim(source.substr(end + 1));
1031 if (next == std::string::npos)
1033 end = next + end + 1;
1035 func(source.substr(start),
true);
1099 template <
typename FUNC>
1100 requires std::is_invocable_v<FUNC, std::string_view, bool>
1104 while ((next = source.find_first_of(
delim)) != std::string::npos)
1106 func(source.substr(0, next),
false);
1107 source.remove_prefix(next + 1);
1109 if ((next = source.find_first_not_of(
delim)) == std::string::npos)
1112 source.remove_prefix(next);
1115 if (!source.empty())
1122 template <
typename RESULT_TYPE = std::
string_view,
string_or_
char DELIM>
1125 std::vector<RESULT_TYPE> result;
1135 template <
typename RESULT_TYPE = std::
string_view>
1139 std::vector<RESULT_TYPE> result;
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>
1154 std::vector<RESULT_TYPE> result;
1163 template <
typename RESULT_TYPE = std::
string_view,
string_or_
char DELIM>
1166 std::vector<RESULT_TYPE> result;
1182 template <std::ranges::range T>
1185 std::stringstream
strm;
1186 for (
auto&&
p : std::forward<T>(source))
1192 template <std::ranges::range T,
string_or_
char DELIM>
1195 std::stringstream
strm;
1197 for (
auto&&
p : std::forward<T>(source))
1207 template <std::ranges::range...
RANGES, string_or_char
DELIM>
1210 std::stringstream
strm;
1213 for (
auto&&
p : std::forward<RANGE>(source))
1219 }(std::forward<RANGES>(
sources)), ...);
1226 template <std::ranges::range T,
string_or_
char DELIM,
string_or_
char LAST_DELIM>
1229 using std::ranges::begin;
1230 using std::ranges::end;
1233 std::stringstream
strm;
1236 auto&&
endit = end(source);
1256 template <std::ranges::range T,
string_or_
char DELIM,
string_or_
char LAST_DELIM,
typename FUNC>
1259 using std::ranges::begin;
1260 using std::ranges::end;
1263 std::stringstream
strm;
1266 auto&&
endit = end(source);
1285 template <std::ranges::range T,
typename FUNC,
string_or_
char DELIM>
1288 std::stringstream
strm;
1290 for (
auto&&
p : source)
1301 template <
string_or_
char NEEDLE,
typename FUNC>
1317 template <
typename RESULT_TYPE = std::
string_view,
string_or_
char NEEDLE>
1320 std::vector<RESULT_TYPE> result;
1331 template <
string_or_
char NEEDLE,
string_or_
char REPLACE>
1351 template <
string_or_
char NEEDLE,
string_or_
char REPLACE>
1354 string_ops::replace(
subject, std::forward<NEEDLE>(
search), std::forward<REPLACE>(replace));
1358 template <
string_or_
char DELIMITER =
char,
string_or_
char ESCAPE =
char>
1363 if constexpr (
requires {
delimiter != escape; })
1375 template <
string_or_
char DELIMITER =
char,
string_or_
char ESCAPE =
char>
1378 ::ghassanpl::string_ops::quote(
subject, std::forward<DELIMITER>(
delimiter), std::forward<ESCAPE>(escape));
1382 template <
string_or_
char DELIMITER =
char,
string_or_
char ESCAPE =
char>
1385 auto result = std::string{
subject };
1386 ::ghassanpl::string_ops::quote(result, std::forward<DELIMITER>(
delimiter), std::forward<ESCAPE>(escape));
1390 template <
string_or_
char DELIMITER =
char,
string_or_
char ESCAPE =
char>
1393 return ::ghassanpl::string_ops::quoted(std::string{
subject }, std::forward<DELIMITER>(
delimiter), std::forward<ESCAPE>(escape));
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>>
1412 template <
string_or_
char ESCAPE =
char>
1428 template <
typename ESCAPE_FUNC,
typename ISPRINTABLE_FUNC = decltype(ascii::ispr
int)>
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)");
1437 std::string::iterator
it;
1447 inline void escape_non_printable(std::string&
subject)
1449 escape_non_printable(
subject, [](
char c) {
1450 return std::format(
"\\x{:02x}",
static_cast<uint8_t>(c));
1454 template <
typename STR,
string_or_
char ESCAPE =
char>
1457 auto result = std::string{
subject };
1462 template <
typename STR>
1465 auto result = std::string{
subject };
1466 ::ghassanpl::string_ops::escape_non_printable(result);
1478 if (ascii::isalnum(c) || c ==
'-' || c ==
'_' || c ==
'.' || c ==
'~')
1485 std::format_to(std::back_inserter(result),
"{:02X}", (
int)(
unsigned char)c);
1494 while (!text.empty())
1498 text.remove_prefix(1);
1499 if (text.size() < 2)
1502 std::from_chars(text.data(), text.data() + 2,
val, 16);
1504 text.remove_prefix(2);
1509 text.remove_prefix(1);
1514#if defined(__cpp_lib_format)
1518 template <
typename CHECKER>
1519 requires std::predicate<CHECKER, std::string_view>
1530#if !__cpp_lib_to_chars && !defined(DOXYGEN)
1534 template <std::
integral T>
1536 return std::from_chars(str.data(), str.data() + str.size(), value, base);
1539 template <std::
floating_po
int T>
1541 return std::from_chars(str.data(), str.data() + str.size(), value,
chars_format);
1551 template <
bool SINGLE>
1556 struct split_range_iterator
1558 const char* RangeStart;
1559 const char* RangeEnd;
1560 const char* SourceEnd;
1563 bool operator!=(
const split_range_iterator&
other)
const {
return RangeStart !=
other.SourceEnd; }
1564 split_range_iterator& operator++(
int) {
1570 split_range_iterator operator++() {
1572 auto se = SourceEnd;
1573 auto sc = SplitChar;
1595 std::pair<const char*, const char*> operator*()
const {
return { RangeStart, RangeEnd }; }
1598 split_range_iterator begin() {
1599 split_range_iterator
it = {
nullptr, mSource.data(), mSource.data() + mSource.size(), mSplit };
1604 split_range_iterator end() {
1605 auto se = mSource.data() + mSource.size();
1606 return {
se,
se,
se, mSplit };
1616 std::string_view mSource;
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>
1633 std::vector<RESULT_TYPE> result;
1666 template <
typename RESULT_TYPE = std::
string_view,
typename T>
1667 requires std::is_arithmetic_v<T>
1673 inline size_t levenshtein_distance(std::string_view
s1, std::string_view
s2)
1675 if (
s1.size() >
s2.size())
1694 if (
s1[
i - 1] ==
s2[
j - 1])
1707 template <std::
integral T>
1708 [[
nodiscard]]
inline auto string_to_number(std::string_view str,
size_t*
idx =
nullptr,
int base = 10)
1710 const auto begin = str.data();
1711 auto end = str.data() + str.size();
1713 if (
auto res = std::from_chars(begin, end, value, base);
idx &&
res.ec == std::error_code{})
1714 *
idx =
size_t(
res.ptr - begin);
1718 template <std::
floating_po
int T>
1719 [[
nodiscard]]
inline auto string_to_number(std::string_view str,
size_t*
idx =
nullptr, std::chars_format format = std::chars_format::general)
1721 const auto begin = str.data();
1722 auto end = str.data() + str.size();
1724 if (
auto res = std::from_chars(begin, end, value, format);
idx &&
res.ec == std::error_code{})
1725 *
idx =
size_t(
res.ptr - begin);
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); }
1743 template <
typename CALLBACK>
1744 requires std::invocable<CALLBACK, size_t, std::string_view, std::string&>
1750 while (!
fmt.empty())
1768 throw std::format_error(
"missing '}' in format string");
1773 aid = string_ops::stoull(
num);
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)...))
Whether the type is a native char type.
Can a type be bit-cast to a native/utf char type?
The type is a string with a 16-bit char type.
The type is a string with an 32-bit char type.
The type is a string with an 8-bit char type.
The type is a stringable or a character.
The type is a string view with a 16-bit char type.
The type is a string view with a 32-bit char type.
The type is a string view with an 8-bit char type.
The type is convertible to a string view with a 16-bit char type.
The type is convertible to a string view with a 32-bit char type.
The type is convertible to a string view with an 8-bit char type.
Whether the type is a native or utf char type.
The type is "stringable", that is, a continuous range of characters.
Whether the type is a utf char type.
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...
constexpr auto hex_digit_chars
All characters that match ascii::isxdigit.
constexpr auto blank_chars
All characters that match ascii::isblank.
constexpr auto control_chars
All characters that match ascii::iscntrl.
constexpr auto punctuation_chars
All characters that match ascii::ispunct.
constexpr auto lowercase_chars
All characters that match ascii::islower.
constexpr auto alphanumeric_chars
All characters that match ascii::isalnum.
constexpr auto whitespace_chars
All characters that match ascii::isspace.
constexpr auto printable_chars
All characters that match ascii::isprint.
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...
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...
constexpr auto digit_chars
All characters that match ascii::isdigit.
constexpr auto identifier_start_chars
All characters that match ascii::isidentstart.
constexpr auto uppercase_chars
All characters that match ascii::isupper.
constexpr auto alphabetic_chars
All characters that match ascii::isalpha.
constexpr auto octal_digit_chars
All characters that match ascii::isodigit.
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]+/)
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...
constexpr auto graphical_chars
All characters that match ascii::isgraph.
std::string capitalize(std::string str) noexcept
Returns a copy of the string with the first letter transformed to upper case if possible.
constexpr auto identifier_chars
All characters that match ascii::isident.
constexpr auto bit_count
Equal to the number of bits in the type.
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.
constexpr CONTAINER to(RANGE &&range, TYPES &&... args)
to<container>();
constexpr __contains_fn contains
contains(range, el)
constexpr bool isascii(char32_t cp) noexcept
Returns true if cp is an ascii codepoint.
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.
std::string_view consume_while(std::string_view &str, FUNC &&pred)
Consumes characters from the beginning of str while they match pred(str[0]).
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.
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...
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,...
char consume_any(std::string_view &str, ARGS &&... args)
Consumes any of the characters in 'chars' if it's the first char of str.
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.
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...
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.
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.
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.
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)
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.
char consume(std::string_view &str)
Consumes and returns the first character in the str, or \0 if no more characters.
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)
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].
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...
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,...
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
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.
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,...
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.
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.
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.
bool consume_at_end(std::string_view &str, char val)
Consumes the last character from str if it matches val.
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.
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,...
std::string_view prefix(std::string_view str, size_t count) noexcept
Returns a substring containing the count leftmost characters of str. Always valid,...
std::string_view consume_n(std::string_view &str, size_t n)
Consumes at most n characters from the beginning of str.
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...
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.