Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
text.h
1#ifndef SOURCEMETA_CORE_TEXT_H_
2#define SOURCEMETA_CORE_TEXT_H_
3
4#ifndef SOURCEMETA_CORE_TEXT_EXPORT
5#include <sourcemeta/core/text_export.h>
6#endif
7
8#include <algorithm> // std::max
9#include <array> // std::array
10#include <charconv> // std::to_chars
11#include <concepts> // std::same_as, std::integral
12#include <cstddef> // std::size_t
13#include <cstdint> // std::int8_t, std::uint64_t
14#include <filesystem> // std::filesystem::path
15#include <ios> // std::streamsize
16#include <limits> // std::numeric_limits
17#include <optional> // std::optional
18#include <ostream> // std::ostream
19#include <string> // std::string
20#include <string_view> // std::string_view
21#include <type_traits> // std::is_nothrow_copy_constructible_v, std::is_nothrow_invocable_r_v, std::remove_cv_t
22#include <utility> // std::pair
23#include <vector> // std::vector
24
33
34namespace sourcemeta::core {
35
49SOURCEMETA_CORE_TEXT_EXPORT
50auto to_title_case(std::string &value) -> void;
51
65template <typename Character>
66 requires std::same_as<Character, char> ||
67 std::same_as<Character, signed char> ||
68 std::same_as<Character, unsigned char> ||
69 std::same_as<Character, wchar_t>
70constexpr auto to_lowercase(const Character character) noexcept -> Character {
71 return (character >= 'A' && character <= 'Z')
72 ? static_cast<Character>(character + ('a' - 'A'))
73 : character;
74}
75
89template <typename Character, typename Traits, typename Allocator>
90 requires requires(Character character) {
91 { to_lowercase(character) } -> std::same_as<Character>;
92 }
93inline auto to_lowercase(std::basic_string<Character, Traits, Allocator> &value)
94 -> void {
95 for (auto &character : value) {
96 character = to_lowercase(character);
97 }
98}
99
113SOURCEMETA_CORE_TEXT_EXPORT
114auto to_lowercase(std::filesystem::path &value) -> void;
115
128template <typename Character>
129 requires std::same_as<Character, char> ||
130 std::same_as<Character, signed char> ||
131 std::same_as<Character, unsigned char> ||
132 std::same_as<Character, wchar_t>
133constexpr auto is_lowercase(const Character character) noexcept -> bool {
134 return character < 'A' || character > 'Z';
135}
136
150template <typename String>
151 requires requires(const String &value) {
152 { is_lowercase(*value.begin()) } -> std::same_as<bool>;
153 }
154inline auto is_lowercase(const String &value) noexcept -> bool {
155 for (const auto character : value) {
156 if (!is_lowercase(character)) {
157 return false;
158 }
159 }
160 return true;
161}
162
176SOURCEMETA_CORE_TEXT_EXPORT
177auto is_lowercase(const std::filesystem::path &value) noexcept -> bool;
178
191template <typename Character>
192 requires std::same_as<Character, char> ||
193 std::same_as<Character, signed char> ||
194 std::same_as<Character, unsigned char> ||
195 std::same_as<Character, wchar_t>
196constexpr auto is_alpha(const Character character) noexcept -> bool {
197 return (character >= 'a' && character <= 'z') ||
198 (character >= 'A' && character <= 'Z');
199}
200
214constexpr auto is_alpha(const std::string_view value) noexcept -> bool {
215 if (value.empty()) {
216 return false;
217 }
218 for (const auto character : value) {
219 if (!is_alpha(character)) {
220 return false;
221 }
222 }
223 return true;
224}
225
237template <typename Character>
238 requires std::same_as<Character, char> ||
239 std::same_as<Character, signed char> ||
240 std::same_as<Character, unsigned char> ||
241 std::same_as<Character, wchar_t>
242constexpr auto is_digit(const Character character) noexcept -> bool {
243 return character >= '0' && character <= '9';
244}
245
259constexpr auto is_digit(const std::string_view value) noexcept -> bool {
260 if (value.empty()) {
261 return false;
262 }
263 for (const auto character : value) {
264 if (!is_digit(character)) {
265 return false;
266 }
267 }
268 return true;
269}
270
283template <typename Character>
284 requires std::same_as<Character, char> ||
285 std::same_as<Character, signed char> ||
286 std::same_as<Character, unsigned char> ||
287 std::same_as<Character, wchar_t>
288constexpr auto is_alphanum(const Character character) noexcept -> bool {
289 return is_alpha(character) || is_digit(character);
290}
291
305constexpr auto is_alphanum(const std::string_view value) noexcept -> bool {
306 if (value.empty()) {
307 return false;
308 }
309 for (const auto character : value) {
310 if (!is_alphanum(character)) {
311 return false;
312 }
313 }
314 return true;
315}
316
332template <typename Character>
333 requires std::same_as<Character, char> ||
334 std::same_as<Character, signed char> ||
335 std::same_as<Character, unsigned char> ||
336 std::same_as<Character, wchar_t>
337constexpr auto is_punctuation(const Character character) noexcept -> bool {
338 return (character >= '!' && character <= '/') ||
339 (character >= ':' && character <= '@') ||
340 (character >= '[' && character <= '`') ||
341 (character >= '{' && character <= '~');
342}
343
359SOURCEMETA_CORE_TEXT_EXPORT
360auto truncate(std::string &input, const std::size_t maximum_length,
361 const std::string_view marker) -> void;
362
374SOURCEMETA_CORE_TEXT_EXPORT
375auto replace(const std::string_view input, const std::string_view target,
376 const std::string_view replacement) -> std::string;
377
391SOURCEMETA_CORE_TEXT_EXPORT
392auto trim(const std::string_view input) noexcept -> std::string_view;
393
406SOURCEMETA_CORE_TEXT_EXPORT
407auto strip_left(const std::string_view input, const char character) noexcept
408 -> std::string_view;
409
422SOURCEMETA_CORE_TEXT_EXPORT
423auto strip_right(const std::string_view input, const char character) noexcept
424 -> std::string_view;
425
438template <typename Predicate>
439 requires std::predicate<Predicate &, char>
440constexpr auto
441strip_left(const std::string_view input, Predicate predicate) noexcept(
442 std::is_nothrow_invocable_r_v<bool, Predicate &, char> &&
443 std::is_nothrow_copy_constructible_v<Predicate>) -> std::string_view {
444 std::string_view result{input};
445 while (!result.empty() && predicate(result.front())) {
446 result.remove_prefix(1);
447 }
448
449 return result;
450}
451
464template <typename Predicate>
465 requires std::predicate<Predicate &, char>
466constexpr auto
467strip_right(const std::string_view input, Predicate predicate) noexcept(
468 std::is_nothrow_invocable_r_v<bool, Predicate &, char> &&
469 std::is_nothrow_copy_constructible_v<Predicate>) -> std::string_view {
470 std::string_view result{input};
471 while (!result.empty() && predicate(result.back())) {
472 result.remove_suffix(1);
473 }
474
475 return result;
476}
477
491template <typename Predicate>
492 requires std::predicate<Predicate &, char>
493constexpr auto trim(const std::string_view input, Predicate predicate) noexcept(
494 std::is_nothrow_invocable_r_v<bool, Predicate &, char> &&
495 std::is_nothrow_copy_constructible_v<Predicate>) -> std::string_view {
496 return strip_right(strip_left(input, predicate), predicate);
497}
498
513constexpr auto unquote(const std::string_view input, const char quote) noexcept
514 -> std::string_view {
515 if (input.size() < 2 || input.front() != quote || input.back() != quote) {
516 return input;
517 }
518
519 auto inner{input};
520 inner.remove_prefix(1);
521 inner.remove_suffix(1);
522 return inner;
523}
524
537SOURCEMETA_CORE_TEXT_EXPORT
538auto pad_left(const std::string_view input, const std::size_t width,
539 const char character) -> std::string;
540
554SOURCEMETA_CORE_TEXT_EXPORT
555auto take_until(const std::string_view input, const char marker) noexcept
556 -> std::string_view;
557
574SOURCEMETA_CORE_TEXT_EXPORT
575auto split_once(const std::string_view input, const char delimiter) noexcept
576 -> std::optional<std::pair<std::string_view, std::string_view>>;
577
593SOURCEMETA_CORE_TEXT_EXPORT
594auto split_once(const std::string_view input,
595 const std::string_view delimiter) noexcept
596 -> std::optional<std::pair<std::string_view, std::string_view>>;
597
614SOURCEMETA_CORE_TEXT_EXPORT
615auto rsplit_once(const std::string_view input, const char delimiter) noexcept
616 -> std::optional<std::pair<std::string_view, std::string_view>>;
617
632template <typename Callback>
633auto split(const std::string_view input, const char delimiter,
634 Callback callback) -> void {
635 std::string_view rest{input};
636 while (true) {
637 const auto next{sourcemeta::core::split_once(rest, delimiter)};
638 if (!next.has_value()) {
639 callback(rest);
640 return;
641 }
642 callback(next->first);
643 rest = next->second;
644 }
645}
646
662inline auto split(const std::string_view input, const char delimiter)
663 -> std::vector<std::string_view> {
664 std::vector<std::string_view> parts;
666 input, delimiter,
667 [&parts](const std::string_view part) -> void { parts.push_back(part); });
668 return parts;
669}
670
685template <typename Range>
686auto join(const Range &items, const std::string_view separator) -> std::string {
687 std::string result;
688 bool first{true};
689 for (const auto &item : items) {
690 if (!first) {
691 result.append(separator);
692 }
693
694 result.append(item);
695 first = false;
696 }
697
698 return result;
699}
700
715template <typename Range>
716auto join_to(std::ostream &stream, const Range &items,
717 const std::string_view separator) -> void {
718 bool first{true};
719 for (const auto &item : items) {
720 if (!first) {
721 stream << separator;
722 }
723 stream << item;
724 first = false;
725 }
726}
727
733template <typename Type>
735 std::integral<Type> && !std::same_as<std::remove_cv_t<Type>, bool> &&
736 sizeof(Type) <= sizeof(std::uint64_t);
737
743template <typename Integer>
745inline constexpr std::size_t DIGITS_CAPACITY{
746 static_cast<std::size_t>(std::numeric_limits<Integer>::digits10) + 1 +
747 (std::numeric_limits<Integer>::is_signed ? 1U : 0U)};
748
754using DigitsBuffer = std::array<char, std::max(DIGITS_CAPACITY<std::uint64_t>,
756
779template <typename Integer, std::size_t Capacity>
781 (Capacity >= DIGITS_CAPACITY<Integer>)
782inline auto digits_view(const Integer value,
783 std::array<char, Capacity> &buffer) noexcept
784 -> std::string_view {
785 const auto result{
786 std::to_chars(buffer.data(), buffer.data() + buffer.size(), value)};
787 return {buffer.data(), static_cast<std::size_t>(result.ptr - buffer.data())};
788}
789
804template <typename Output, typename Integer>
805 requires text_spellable_integer<Integer>
806inline auto digits_append(Output &output, const Integer value) -> void {
807 std::array<char, DIGITS_CAPACITY<Integer>> buffer;
808 const auto digits{digits_view(value, buffer)};
809 output.append(digits.data(), digits.size());
810}
811
828template <typename CharT, typename Traits, typename Integer>
829 requires text_spellable_integer<Integer>
830inline auto digits_write(std::basic_ostream<CharT, Traits> &stream,
831 const Integer value) -> void {
832 std::array<char, DIGITS_CAPACITY<Integer>> buffer;
833 const auto digits{digits_view(value, buffer)};
834 stream.write(digits.data(), static_cast<std::streamsize>(digits.size()));
835}
836
850inline auto hex_digit_value(const char character) noexcept -> std::int8_t {
851 // Indexed by byte value: ASCII '0'-'9', 'A'-'F', and 'a'-'f' map to their
852 // hexadecimal value, everything else to -1
853 static constexpr std::array<std::int8_t, 256> TABLE{
854 {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
855 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
856 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5,
857 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1,
858 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
859 -1, -1, -1, -1, -1, -1, -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1,
860 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
861 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
862 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
863 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
864 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
865 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
866 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
867 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
868 -1, -1, -1, -1}};
869 return TABLE[static_cast<unsigned char>(character)];
870}
871
884inline auto is_hex_digit(const char character) noexcept -> bool {
885 return hex_digit_value(character) >= 0;
886}
887
901inline auto is_percent_triplet(const std::string_view input,
902 const std::size_t position) noexcept -> bool {
903 return input.size() >= 3 && position < input.size() - 2 &&
904 input[position] == '%' && is_hex_digit(input[position + 1]) &&
905 is_hex_digit(input[position + 2]);
906}
907
923SOURCEMETA_CORE_TEXT_EXPORT
924auto hex_to_bytes(const std::string_view input,
925 const bool allow_odd_length = false)
926 -> std::optional<std::string>;
927
938SOURCEMETA_CORE_TEXT_EXPORT
939auto bytes_to_hex(const std::string_view input) -> std::string;
940
956inline auto equals_ignore_case(const std::string_view left,
957 const std::string_view right) noexcept -> bool {
958 if (left.size() != right.size()) {
959 return false;
960 }
961
962 for (std::size_t index{0}; index < left.size(); ++index) {
963 if (to_lowercase(left[index]) != to_lowercase(right[index])) {
964 return false;
965 }
966 }
967
968 return true;
969}
970
985inline auto starts_with_ignore_case(const std::string_view value,
986 const std::string_view prefix) noexcept
987 -> bool {
988 if (value.size() < prefix.size()) {
989 return false;
990 }
991
992 for (std::size_t index{0}; index < prefix.size(); ++index) {
993 if (to_lowercase(value[index]) != to_lowercase(prefix[index])) {
994 return false;
995 }
996 }
997
998 return true;
999}
1000
1015inline auto less_ignore_case(const std::string_view left,
1016 const std::string_view right) noexcept -> bool {
1017 const auto length{left.size() < right.size() ? left.size() : right.size()};
1018 for (std::size_t index{0}; index < length; ++index) {
1019 const auto left_lower{to_lowercase(left[index])};
1020 const auto right_lower{to_lowercase(right[index])};
1021 if (left_lower != right_lower) {
1022 return left_lower < right_lower;
1023 }
1024 }
1025
1026 return left.size() < right.size();
1027}
1028
1040SOURCEMETA_CORE_TEXT_EXPORT
1041auto squeeze(const std::string_view input, const char character) -> std::string;
1042
1058template <typename Output>
1059auto squeeze(const std::string_view input, const char character, Output &output)
1060 -> void {
1061 bool in_run{false};
1062 for (const auto value : input) {
1063 if (value == character) {
1064 if (!in_run) {
1065 output.push_back(value);
1066 }
1067
1068 in_run = true;
1069 } else {
1070 output.push_back(value);
1071 in_run = false;
1072 }
1073 }
1074}
1075
1091SOURCEMETA_CORE_TEXT_EXPORT
1092auto remove_suffix_ignore_case(const std::string_view input,
1093 const std::string_view suffix) noexcept
1094 -> std::string_view;
1095
1096} // namespace sourcemeta::core
1097
1098#endif
@ Type
The token type does not match the expected media type.
Definition jose_verify.h:216
constexpr auto is_digit(const char character) -> bool
Definition numeric_util.h:51
SOURCEMETA_CORE_TEXT_EXPORT auto truncate(std::string &input, const std::size_t maximum_length, const std::string_view marker) -> void
SOURCEMETA_CORE_TEXT_EXPORT auto to_title_case(std::string &value) -> void
auto join_to(std::ostream &stream, const Range &items, const std::string_view separator) -> void
Definition text.h:716
SOURCEMETA_CORE_TEXT_EXPORT auto bytes_to_hex(const std::string_view input) -> std::string
constexpr auto is_alphanum(const Character character) noexcept -> bool
Definition text.h:288
auto less_ignore_case(const std::string_view left, const std::string_view right) noexcept -> bool
Definition text.h:1015
constexpr auto is_alpha(const Character character) noexcept -> bool
Definition text.h:196
constexpr auto is_lowercase(const Character character) noexcept -> bool
Definition text.h:133
auto is_hex_digit(const char character) noexcept -> bool
Definition text.h:884
std::array< char, std::max(DIGITS_CAPACITY< std::uint64_t >, DIGITS_CAPACITY< std::int64_t >)> DigitsBuffer
Definition text.h:754
auto digits_view(const Integer value, std::array< char, Capacity > &buffer) noexcept -> std::string_view
Definition text.h:782
auto split(const std::string_view input, const char delimiter, Callback callback) -> void
Definition text.h:633
SOURCEMETA_CORE_TEXT_EXPORT auto hex_to_bytes(const std::string_view input, const bool allow_odd_length=false) -> std::optional< std::string >
auto equals_ignore_case(const std::string_view left, const std::string_view right) noexcept -> bool
Definition text.h:956
SOURCEMETA_CORE_TEXT_EXPORT auto strip_left(const std::string_view input, const char character) noexcept -> std::string_view
constexpr auto unquote(const std::string_view input, const char quote) noexcept -> std::string_view
Definition text.h:513
SOURCEMETA_CORE_TEXT_EXPORT auto replace(const std::string_view input, const std::string_view target, const std::string_view replacement) -> std::string
auto hex_digit_value(const char character) noexcept -> std::int8_t
Definition text.h:850
SOURCEMETA_CORE_TEXT_EXPORT auto remove_suffix_ignore_case(const std::string_view input, const std::string_view suffix) noexcept -> std::string_view
auto digits_write(std::basic_ostream< CharT, Traits > &stream, const Integer value) -> void
Definition text.h:830
auto join(const Range &items, const std::string_view separator) -> std::string
Definition text.h:686
SOURCEMETA_CORE_TEXT_EXPORT auto strip_right(const std::string_view input, const char character) noexcept -> std::string_view
constexpr auto is_punctuation(const Character character) noexcept -> bool
Definition text.h:337
SOURCEMETA_CORE_TEXT_EXPORT auto trim(const std::string_view input) noexcept -> std::string_view
SOURCEMETA_CORE_TEXT_EXPORT auto squeeze(const std::string_view input, const char character) -> std::string
SOURCEMETA_CORE_TEXT_EXPORT auto take_until(const std::string_view input, const char marker) noexcept -> std::string_view
constexpr auto to_lowercase(const Character character) noexcept -> Character
Definition text.h:70
SOURCEMETA_CORE_TEXT_EXPORT auto rsplit_once(const std::string_view input, const char delimiter) noexcept -> std::optional< std::pair< std::string_view, std::string_view > >
SOURCEMETA_CORE_TEXT_EXPORT auto pad_left(const std::string_view input, const std::size_t width, const char character) -> std::string
auto is_percent_triplet(const std::string_view input, const std::size_t position) noexcept -> bool
Definition text.h:901
SOURCEMETA_CORE_TEXT_EXPORT auto split_once(const std::string_view input, const char delimiter) noexcept -> std::optional< std::pair< std::string_view, std::string_view > >
constexpr std::size_t DIGITS_CAPACITY
Definition text.h:745
auto digits_append(Output &output, const Integer value) -> void
Definition text.h:806
auto starts_with_ignore_case(const std::string_view value, const std::string_view prefix) noexcept -> bool
Definition text.h:985