Branch data Line data Source code
1 : : // Copyright (c) 2019-present The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_UTIL_STRING_H
6 : : #define BITCOIN_UTIL_STRING_H
7 : :
8 : : #include <span.h>
9 : :
10 : : #include <array>
11 : : #include <cstdint>
12 : : #include <cstring>
13 : : #include <locale>
14 : : #include <sstream>
15 : : #include <string> // IWYU pragma: export
16 : : #include <string_view> // IWYU pragma: export
17 : : #include <vector>
18 : :
19 : : namespace util {
20 : : /**
21 : : * @brief A wrapper for a compile-time partially validated format string
22 : : *
23 : : * This struct can be used to enforce partial compile-time validation of format
24 : : * strings, to reduce the likelihood of tinyformat throwing exceptions at
25 : : * run-time. Validation is partial to try and prevent the most common errors
26 : : * while avoiding re-implementing the entire parsing logic.
27 : : */
28 : : template <unsigned num_params>
29 : : struct ConstevalFormatString {
30 : : const char* const fmt;
31 : : consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); }
32 : : constexpr static void Detail_CheckNumFormatSpecifiers(const char* str)
33 : : {
34 : : unsigned count_normal{0}; // Number of "normal" specifiers, like %s
35 : : unsigned count_pos{0}; // Max number in positional specifier, like %8$s
36 : : for (auto it{str}; *it != '\0'; ++it) {
37 : : if (*it != '%' || *++it == '%') continue; // Skip escaped %%
38 : :
39 : : auto add_arg = [&] {
40 : : unsigned maybe_num{0};
41 : : while ('0' <= *it && *it <= '9') {
42 : : maybe_num *= 10;
43 : : maybe_num += *it - '0';
44 : : ++it;
45 : : }
46 : :
47 : : if (*it == '$') {
48 : : ++it;
49 : : // Positional specifier, like %8$s
50 : : if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
51 : : count_pos = std::max(count_pos, maybe_num);
52 : : } else {
53 : : // Non-positional specifier, like %s
54 : : ++count_normal;
55 : : }
56 : : };
57 : :
58 : : // Increase argument count and consume positional specifier, if present.
59 : : add_arg();
60 : :
61 : : // Consume flags.
62 : : while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
63 : :
64 : : auto parse_size = [&] {
65 : : if (*it == '*') {
66 : : ++it;
67 : : add_arg();
68 : : } else {
69 : : while ('0' <= *it && *it <= '9') ++it;
70 : : }
71 : : };
72 : :
73 : : // Consume dynamic or static width value.
74 : : parse_size();
75 : :
76 : : // Consume dynamic or static precision value.
77 : : if (*it == '.') {
78 : : ++it;
79 : : parse_size();
80 : : }
81 : :
82 : : if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
83 : :
84 : : // Length and type in "[flags][width][.precision][length]type"
85 : : // is not checked. Parsing continues with the next '%'.
86 : : }
87 : : if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
88 : : unsigned count{count_normal | count_pos};
89 : : if (num_params != count) throw "Format specifier count must match the argument count!";
90 : : }
91 : : };
92 : :
93 : : void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
94 : :
95 : : /** Split a string on any char found in separators, returning a vector.
96 : : *
97 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
98 : : *
99 : : * Note that this function does not care about braces, so splitting
100 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
101 : : */
102 : : template <typename T = Span<const char>>
103 : 634765 : std::vector<T> Split(const Span<const char>& sp, std::string_view separators)
104 : : {
105 : 634765 : std::vector<T> ret;
106 : 634765 : auto it = sp.begin();
107 : 634765 : auto start = it;
108 [ + + ]: 152568118 : while (it != sp.end()) {
109 [ + + ]: 151933353 : if (separators.find(*it) != std::string::npos) {
110 [ + - ]: 809647 : ret.emplace_back(start, it);
111 : 809647 : start = it + 1;
112 : : }
113 : 151933353 : ++it;
114 : : }
115 [ + - ]: 634765 : ret.emplace_back(start, it);
116 : 634765 : return ret;
117 : 0 : }
118 : :
119 : : /** Split a string on every instance of sep, returning a vector.
120 : : *
121 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
122 : : *
123 : : * Note that this function does not care about braces, so splitting
124 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
125 : : */
126 : : template <typename T = Span<const char>>
127 : 632343 : std::vector<T> Split(const Span<const char>& sp, char sep)
128 : : {
129 : 632343 : return Split<T>(sp, std::string_view{&sep, 1});
130 : : }
131 : :
132 : 42455 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
133 : : {
134 [ + - ][ + - : 42455 : return Split<std::string>(str, sep);
+ - + - +
- ]
[ + - - - ]
135 : : }
136 : :
137 : 1056 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
138 : : {
139 [ + - ]: 1056 : return Split<std::string>(str, separators);
140 : : }
141 : :
142 : 63605 : [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
143 : : {
144 : 63605 : std::string::size_type front = str.find_first_not_of(pattern);
145 [ + + ]: 63605 : if (front == std::string::npos) {
146 : 640 : return {};
147 : : }
148 : 62965 : std::string::size_type end = str.find_last_not_of(pattern);
149 : 62965 : return str.substr(front, end - front + 1);
150 : : }
151 : :
152 : 5929 : [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
153 : : {
154 : 5929 : return std::string(TrimStringView(str, pattern));
155 : : }
156 : :
157 : 0 : [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
158 : : {
159 [ # # ]: 0 : if (str.ends_with(suffix)) {
160 : 0 : return str.substr(0, str.size() - suffix.size());
161 : : }
162 : 0 : return str;
163 : : }
164 : :
165 : 3909936 : [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
166 : : {
167 [ + + ]: 3909936 : if (str.substr(0, prefix.size()) == prefix) {
168 : 3909097 : return str.substr(prefix.size());
169 : : }
170 : 839 : return str;
171 : : }
172 : :
173 : 840 : [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
174 : : {
175 : 840 : return std::string(RemovePrefixView(str, prefix));
176 : : }
177 : :
178 : : /**
179 : : * Join all container items. Typically used to concatenate strings but accepts
180 : : * containers with elements of any type.
181 : : *
182 : : * @param container The items to join
183 : : * @param separator The separator
184 : : * @param unary_op Apply this operator to each item
185 : : */
186 : : template <typename C, typename S, typename UnaryOp>
187 : : // NOLINTNEXTLINE(misc-no-recursion)
188 : 106423 : auto Join(const C& container, const S& separator, UnaryOp unary_op)
189 : : {
190 : 106423 : decltype(unary_op(*container.begin())) ret;
191 : 106423 : bool first{true};
192 [ + + ]: 383284 : for (const auto& item : container) {
193 [ + + ]: 347513 : if (!first) ret += separator;
[ + + + - ]
194 [ + - ]: 553722 : ret += unary_op(item);
[ + - + - ]
195 : 276861 : first = false;
196 : : }
197 : 106423 : return ret;
198 : 0 : }
199 : :
200 : : template <typename C, typename S>
201 : 102173 : auto Join(const C& container, const S& separator)
202 : : {
203 [ + - ]: 301331 : return Join(container, separator, [](const auto& i) { return i; });
204 : : }
205 : :
206 : : /**
207 : : * Create an unordered multi-line list of items.
208 : : */
209 : 67 : inline std::string MakeUnorderedList(const std::vector<std::string>& items)
210 : : {
211 [ + - + - ]: 469 : return Join(items, "\n", [](const std::string& item) { return "- " + item; });
212 : : }
213 : :
214 : : /**
215 : : * Check if a string does not contain any embedded NUL (\0) characters
216 : : */
217 : 3419990 : [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
218 : : {
219 [ + + ]: 249648636 : for (auto c : str) {
[ + + + + ]
[ + + + +
+ + + + +
+ ]
220 [ + + ]: 246455709 : if (c == 0) return false;
[ + + + + ]
[ + + + +
+ + + + +
- ]
221 : : }
222 : : return true;
223 : : }
224 : :
225 : : /**
226 : : * Locale-independent version of std::to_string
227 : : */
228 : : template <typename T>
229 : 173942 : std::string ToString(const T& t)
230 : : {
231 : 173942 : std::ostringstream oss;
232 [ + - + - ]: 173942 : oss.imbue(std::locale::classic());
233 [ + - + - ]: 173942 : oss << t;
[ # # ]
234 : 173942 : return oss.str();
235 : 173942 : }
236 : :
237 : : /**
238 : : * Check whether a container begins with the given prefix.
239 : : */
240 : : template <typename T1, size_t PREFIX_LEN>
241 [ + + ]: 859693642 : [[nodiscard]] inline bool HasPrefix(const T1& obj,
242 : : const std::array<uint8_t, PREFIX_LEN>& prefix)
243 : : {
244 [ + - + + ]: 1717577211 : return obj.size() >= PREFIX_LEN &&
245 [ + + ]: 859693642 : std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
246 : : }
247 : : } // namespace util
248 : :
249 : : #endif // BITCOIN_UTIL_STRING_H
|