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 : : #include <tinyformat.h>
10 : :
11 : : #include <array>
12 : : #include <cstdint>
13 : : #include <cstring>
14 : : #include <locale>
15 : : #include <sstream>
16 : : #include <string> // IWYU pragma: export
17 : : #include <string_view> // IWYU pragma: export
18 : : #include <vector>
19 : :
20 : : namespace util {
21 : : /**
22 : : * @brief A wrapper for a compile-time partially validated format string
23 : : *
24 : : * This struct can be used to enforce partial compile-time validation of format
25 : : * strings, to reduce the likelihood of tinyformat throwing exceptions at
26 : : * run-time. Validation is partial to try and prevent the most common errors
27 : : * while avoiding re-implementing the entire parsing logic.
28 : : *
29 : : * @note Counting of `*` dynamic width and precision fields (such as `%*c`,
30 : : * `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as
31 : : * they are not used in the codebase. Usage of these fields is not counted and
32 : : * can lead to run-time exceptions. Code wanting to use the `*` specifier can
33 : : * side-step this struct and call tinyformat directly.
34 : : */
35 : : template <unsigned num_params>
36 : : struct ConstevalFormatString {
37 : : const char* const fmt;
38 : : consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); }
39 : 40 : constexpr static void Detail_CheckNumFormatSpecifiers(std::string_view str)
40 : : {
41 : 40 : unsigned count_normal{0}; // Number of "normal" specifiers, like %s
42 : 40 : unsigned count_pos{0}; // Max number in positional specifier, like %8$s
43 [ + + ]: 129 : for (auto it{str.begin()}; it < str.end();) {
44 [ + + ]: 95 : if (*it != '%') {
45 : 52 : ++it;
46 : 52 : continue;
47 : : }
48 : :
49 [ + + ]: 43 : if (++it >= str.end()) throw "Format specifier incorrectly terminated by end of string";
50 [ + + ]: 42 : if (*it == '%') {
51 : : // Percent escape: %%
52 : 5 : ++it;
53 : 5 : continue;
54 : : }
55 : :
56 : 37 : unsigned maybe_num{0};
57 [ + + ]: 66 : while ('0' <= *it && *it <= '9') {
58 : 29 : maybe_num *= 10;
59 : 29 : maybe_num += *it - '0';
60 : 29 : ++it;
61 : : };
62 : :
63 [ + + ]: 37 : if (*it == '$') {
64 : : // Positional specifier, like %8$s
65 [ + + ]: 20 : if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
66 : 16 : count_pos = std::max(count_pos, maybe_num);
67 [ + + ]: 16 : if (++it >= str.end()) throw "Format specifier incorrectly terminated by end of string";
68 : : } else {
69 : : // Non-positional specifier, like %s
70 : 17 : ++count_normal;
71 : 17 : ++it;
72 : : }
73 : : // The remainder "[flags][width][.precision][length]type" of the
74 : : // specifier is not checked. Parsing continues with the next '%'.
75 : : }
76 [ + + + + ]: 34 : if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
77 : 33 : unsigned count{count_normal | count_pos};
78 [ + + ]: 33 : if (num_params != count) throw "Format specifier count must match the argument count!";
79 : 28 : }
80 : : };
81 : :
82 : : void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
83 : :
84 : : /** Split a string on any char found in separators, returning a vector.
85 : : *
86 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
87 : : *
88 : : * Note that this function does not care about braces, so splitting
89 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
90 : : */
91 : : template <typename T = Span<const char>>
92 : 25348 : std::vector<T> Split(const Span<const char>& sp, std::string_view separators)
93 : : {
94 : 25348 : std::vector<T> ret;
95 : 25348 : auto it = sp.begin();
96 : 25348 : auto start = it;
97 [ + + ]: 657945 : while (it != sp.end()) {
98 [ + + ]: 632597 : if (separators.find(*it) != std::string::npos) {
99 [ + - ]: 13400 : ret.emplace_back(start, it);
100 : 13400 : start = it + 1;
101 : : }
102 : 632597 : ++it;
103 : : }
104 [ + - ]: 25348 : ret.emplace_back(start, it);
105 : 25348 : return ret;
106 : 0 : }
107 : :
108 : : /** Split a string on every instance of sep, returning a vector.
109 : : *
110 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
111 : : *
112 : : * Note that this function does not care about braces, so splitting
113 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
114 : : */
115 : : template <typename T = Span<const char>>
116 : 22477 : std::vector<T> Split(const Span<const char>& sp, char sep)
117 : : {
118 : 22477 : return Split<T>(sp, std::string_view{&sep, 1});
119 : : }
120 : :
121 : 19806 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
122 : : {
123 [ + - ][ - - : 19806 : return Split<std::string>(str, sep);
- - + - +
- ]
[ # # # # ]
124 : : }
125 : :
126 : 2752 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
127 : : {
128 [ + - ][ + - : 2752 : return Split<std::string>(str, separators);
+ - + - +
- + - + -
+ - ]
129 : : }
130 : :
131 : 667417 : [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
132 : : {
133 : 667417 : std::string::size_type front = str.find_first_not_of(pattern);
134 [ + + ]: 667417 : if (front == std::string::npos) {
135 : 33 : return {};
136 : : }
137 : 667384 : std::string::size_type end = str.find_last_not_of(pattern);
138 : 667384 : return str.substr(front, end - front + 1);
139 : : }
140 : :
141 : 370774 : [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
142 : : {
143 : 370774 : return std::string(TrimStringView(str, pattern));
144 : : }
145 : :
146 : 5 : [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
147 : : {
148 [ + - ]: 5 : if (str.ends_with(suffix)) {
149 : 5 : return str.substr(0, str.size() - suffix.size());
150 : : }
151 : 0 : return str;
152 : : }
153 : :
154 : 306316 : [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
155 : : {
156 [ + + ]: 306316 : if (str.substr(0, prefix.size()) == prefix) {
157 : 19 : return str.substr(prefix.size());
158 : : }
159 : 306297 : return str;
160 : : }
161 : :
162 : 5 : [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
163 : : {
164 : 5 : return std::string(RemovePrefixView(str, prefix));
165 : : }
166 : :
167 : : /**
168 : : * Join all container items. Typically used to concatenate strings but accepts
169 : : * containers with elements of any type.
170 : : *
171 : : * @param container The items to join
172 : : * @param separator The separator
173 : : * @param unary_op Apply this operator to each item
174 : : */
175 : : template <typename C, typename S, typename UnaryOp>
176 : : // NOLINTNEXTLINE(misc-no-recursion)
177 : 13617 : auto Join(const C& container, const S& separator, UnaryOp unary_op)
178 : : {
179 : 13617 : decltype(unary_op(*container.begin())) ret;
180 : 13617 : bool first{true};
181 [ + + ]: 72585 : for (const auto& item : container) {
182 [ + + ]: 93185 : if (!first) ret += separator;
[ + + + - ]
183 [ + - ]: 117936 : ret += unary_op(item);
[ + - + - ]
184 : 58968 : first = false;
185 : : }
186 : 13617 : return ret;
187 : 0 : }
188 : :
189 : : template <typename C, typename S>
190 : 11394 : auto Join(const C& container, const S& separator)
191 : : {
192 [ + - ]: 32350 : return Join(container, separator, [](const auto& i) { return i; });
193 : : }
194 : :
195 : : /**
196 : : * Create an unordered multi-line list of items.
197 : : */
198 : 80 : inline std::string MakeUnorderedList(const std::vector<std::string>& items)
199 : : {
200 [ + - + - ]: 560 : return Join(items, "\n", [](const std::string& item) { return "- " + item; });
201 : : }
202 : :
203 : : /**
204 : : * Check if a string does not contain any embedded NUL (\0) characters
205 : : */
206 : 46248 : [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
207 : : {
208 [ + + + + : 1007237 : for (auto c : str) {
+ + + + +
+ ][ + + ]
[ + + + + ]
209 [ + + + - : 961011 : if (c == 0) return false;
+ + + + +
- ][ + + ]
[ + + + + ]
210 : : }
211 : : return true;
212 : : }
213 : :
214 : : /**
215 : : * Locale-independent version of std::to_string
216 : : */
217 : : template <typename T>
218 : 226259 : std::string ToString(const T& t)
219 : : {
220 : 226259 : std::ostringstream oss;
221 [ + - + - ]: 226259 : oss.imbue(std::locale::classic());
222 [ + - + - ]: 226259 : oss << t;
[ # # ]
223 : 226259 : return oss.str();
224 : 226259 : }
225 : :
226 : : /**
227 : : * Check whether a container begins with the given prefix.
228 : : */
229 : : template <typename T1, size_t PREFIX_LEN>
230 [ + + ]: 718873 : [[nodiscard]] inline bool HasPrefix(const T1& obj,
231 : : const std::array<uint8_t, PREFIX_LEN>& prefix)
232 : : {
233 [ + - + + ]: 1436194 : return obj.size() >= PREFIX_LEN &&
234 [ + + ]: 718873 : std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
235 : : }
236 : : } // namespace util
237 : :
238 : : namespace tinyformat {
239 : : template <typename... Args>
240 : 302385 : std::string format(util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args)
241 : : {
242 : 302385 : return format(fmt.fmt, args...);
243 : : }
244 : : } // namespace tinyformat
245 : :
246 : : #endif // BITCOIN_UTIL_STRING_H
|