Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2026 by Thun Lu. All rights reserved.
3 : : * Author: Thun Lu <thun.lu@zohomail.cn>
4 : : * Repo: https://github.com/thun-res/vlink
5 : : * _ __ __ _ __
6 : : * | | / / / / (_) ____ / /__
7 : : * | | / / / / / / / __ \ / //_/
8 : : * | |/ / / /___ / / / / / / / ,<
9 : : * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10 : : *
11 : : * Licensed under the Apache License, Version 2.0 (the "License");
12 : : * you may not use this file except in compliance with the License.
13 : : * You may obtain a copy of the License at
14 : : *
15 : : * http://www.apache.org/licenses/LICENSE-2.0
16 : : *
17 : : * Unless required by applicable law or agreed to in writing, software
18 : : * distributed under the License is distributed on an "AS IS" BASIS,
19 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 : : * See the License for the specific language governing permissions and
21 : : * limitations under the License.
22 : : */
23 : :
24 : : #include "./base/helpers.h"
25 : :
26 : : #include <cctype>
27 : : #include <charconv>
28 : : #include <chrono>
29 : : #include <codecvt>
30 : : #include <cstdio>
31 : : #include <cstring>
32 : : #include <cwchar>
33 : : #include <filesystem>
34 : : #include <iomanip>
35 : : #include <locale>
36 : : #include <sstream>
37 : : #include <string>
38 : : #include <type_traits>
39 : : #include <utility>
40 : : #include <vector>
41 : :
42 : : #ifdef _WIN32
43 : : #include <Windows.h>
44 : : #endif
45 : :
46 : : namespace vlink {
47 : :
48 : : namespace Helpers {
49 : :
50 : 1211 : int to_int(const std::string& str, int dv) noexcept {
51 [ + + ]: 1211 : if (str.empty()) {
52 : 1142 : return dv;
53 : : }
54 : :
55 : : try {
56 : 69 : size_t pos = 0;
57 [ + + ]: 69 : auto value = std::stoi(str, &pos, 0);
58 : :
59 [ + + ]: 66 : if (pos != str.size()) {
60 : 2 : return dv;
61 : : }
62 : :
63 : 64 : return value;
64 [ - + ]: 3 : } catch (std::exception&) {
65 : 3 : return dv;
66 : 3 : }
67 : : }
68 : :
69 : 10 : int64_t to_long(const std::string& str, int64_t dv, int offset) noexcept {
70 [ + + ]: 10 : if (str.empty()) {
71 : 1 : return dv;
72 : : }
73 : :
74 : : try {
75 : 9 : size_t pos = 0;
76 [ + + ]: 9 : auto value = std::stoll(str, &pos, 0);
77 : :
78 [ + + ]: 7 : if (pos != str.size() - offset) {
79 : 1 : return dv;
80 : : }
81 : :
82 : 6 : return value;
83 [ - + ]: 2 : } catch (std::exception&) {
84 : 2 : return dv;
85 : 2 : }
86 : : }
87 : :
88 : 735 : std::string double_to_string(double value, int precision) noexcept {
89 : : // char buffer[64];
90 : :
91 : : // auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), value, std::chars_format::fixed, precision);
92 : :
93 : : // if (ec == std::errc()) {
94 : : // return std::string(buffer, ptr);
95 : : // }
96 : :
97 : : // return {};
98 : :
99 [ + + ]: 735 : thread_local std::ostringstream ss;
100 : 735 : ss.clear();
101 : 735 : ss.str("");
102 : :
103 : 735 : ss << std::fixed << std::setprecision(precision) << value;
104 : :
105 : 735 : return ss.str();
106 : : }
107 : :
108 : : #if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 11
109 : : enum class CharsFormat : uint8_t { scientific = 1U, fixed = 2U, hex = 4U, general = 3U };
110 : : #else
111 : : using CharsFormat = std::chars_format;
112 : : #endif
113 : :
114 : : template <typename FloatT, typename = void>
115 : : struct HasFloatToChars final : std::false_type {};
116 : :
117 : : template <typename FloatT>
118 : : struct HasFloatToChars<FloatT, std::void_t<decltype(std::to_chars(std::declval<char*>(), std::declval<char*>(),
119 : : std::declval<FloatT>(), CharsFormat::general, 6))>>
120 : : final : std::true_type {};
121 : :
122 : : #ifdef __QNX__
123 : : template <typename FloatT>
124 : : constexpr bool kUseFloatToChars = HasFloatToChars<FloatT>::value && !std::is_same_v<FloatT, long double>;
125 : : #else
126 : : template <typename FloatT>
127 : : constexpr bool kUseFloatToChars = HasFloatToChars<FloatT>::value;
128 : : #endif
129 : :
130 : : template <typename FloatT>
131 : 14 : size_t format_floating_to_impl(char* buf, size_t buflen, FloatT value) noexcept {
132 : : if constexpr (kUseFloatToChars<FloatT>) {
133 : 14 : auto result = std::to_chars(buf, buf + buflen, value, CharsFormat::general, 6);
134 : :
135 [ + - ]: 14 : if VLIKELY (result.ec == std::errc()) {
136 : 14 : return static_cast<size_t>(result.ptr - buf);
137 : : }
138 : :
139 : 0 : return 0;
140 : : } else {
141 : : const char* spec = std::is_same_v<FloatT, long double> ? "%Lg" : "%g"; // NOLINT(google-runtime-float)
142 : : int len = std::snprintf(buf, buflen, spec, value);
143 : :
144 : : if VLIKELY (len > 0 && static_cast<size_t>(len) < buflen) {
145 : : return static_cast<size_t>(len);
146 : : }
147 : :
148 : : return 0;
149 : : }
150 : : }
151 : :
152 : 4 : size_t format_floating_to(char* buf, size_t buflen, float value) noexcept {
153 : 4 : return format_floating_to_impl(buf, buflen, value);
154 : : }
155 : :
156 : 9 : size_t format_floating_to(char* buf, size_t buflen, double value) noexcept {
157 : 9 : return format_floating_to_impl(buf, buflen, value);
158 : : }
159 : :
160 : 1 : size_t format_floating_to(char* buf, size_t buflen, long double value) noexcept { // NOLINT(google-runtime-float)
161 : 1 : return format_floating_to_impl(buf, buflen, value);
162 : : }
163 : :
164 : : template <typename FloatT>
165 : 78 : size_t format_floating_spec_impl(char* buf, size_t buflen, FloatT value, char type, int precision, bool alt) noexcept {
166 [ + + ]: 78 : if VUNLIKELY (buflen == 0U) {
167 : 1 : return 0U;
168 : : }
169 : :
170 : : if constexpr (kUseFloatToChars<FloatT>) {
171 [ + + + + : 77 : if VLIKELY (!alt || (type != 'g' && type != 'G')) {
+ - + + +
+ ]
172 : 74 : auto chars_fmt = CharsFormat::general;
173 : 74 : bool upper = false;
174 : :
175 [ + + + + : 74 : switch (type) {
+ + + + ]
176 : 3 : case 'F':
177 : 3 : upper = true;
178 : : [[fallthrough]];
179 : 40 : case 'f':
180 : 40 : chars_fmt = CharsFormat::fixed;
181 : :
182 [ + + ]: 40 : if (precision < 0) {
183 : 13 : precision = 6;
184 : : }
185 : :
186 : 40 : break;
187 : 3 : case 'E':
188 : 3 : upper = true;
189 : : [[fallthrough]];
190 : 12 : case 'e':
191 : 12 : chars_fmt = CharsFormat::scientific;
192 : :
193 [ + + ]: 12 : if (precision < 0) {
194 : 5 : precision = 6;
195 : : }
196 : :
197 : 12 : break;
198 : 2 : case 'A':
199 : 2 : upper = true;
200 : : [[fallthrough]];
201 : 6 : case 'a':
202 : 6 : chars_fmt = CharsFormat::hex;
203 : 6 : break;
204 : 2 : case 'G':
205 : 2 : upper = true;
206 : : [[fallthrough]];
207 : 16 : default:
208 [ + + ]: 16 : if (precision < 0) {
209 : 10 : precision = 6;
210 : : }
211 : :
212 : 16 : break;
213 : : }
214 : :
215 [ + + ]: 74 : const auto result = precision >= 0 ? std::to_chars(buf, buf + buflen, value, chars_fmt, precision)
216 : 5 : : std::to_chars(buf, buf + buflen, value, chars_fmt);
217 : :
218 [ - + ]: 74 : if VUNLIKELY (result.ec != std::errc()) {
219 : 0 : return 0U;
220 : : }
221 : :
222 : 74 : auto size = static_cast<size_t>(result.ptr - buf);
223 : :
224 [ + + ]: 74 : if VUNLIKELY (alt) {
225 [ - + ]: 11 : const char exponent = chars_fmt == CharsFormat::hex ? 'p' : 'e';
226 [ + - - + ]: 11 : const size_t start = size > 0U && buf[0] == '-' ? 1U : 0U;
227 : :
228 [ + - + - : 11 : if (start < size && buf[start] >= '0' && buf[start] <= '9') {
+ + ]
229 : 10 : size_t point = size;
230 : :
231 [ + + ]: 20 : for (size_t i = start; i < size; ++i) {
232 [ + + ]: 16 : if (buf[i] == '.') {
233 : 3 : point = 0U;
234 : 3 : break;
235 : : }
236 : :
237 [ + + ]: 13 : if (buf[i] == exponent) {
238 : 3 : point = i;
239 : 3 : break;
240 : : }
241 : : }
242 : :
243 [ + + ]: 10 : if (point != 0U) {
244 [ + + ]: 7 : if VUNLIKELY (size == buflen) {
245 : 1 : return 0U;
246 : : }
247 : :
248 : 6 : std::memmove(buf + point + 1U, buf + point, size - point);
249 : 6 : buf[point] = '.';
250 : 6 : ++size;
251 : : }
252 : : }
253 : : }
254 : :
255 [ + + ]: 73 : if VUNLIKELY (upper) {
256 [ + + ]: 70 : for (size_t i = 0U; i < size; ++i) {
257 [ + + + - ]: 60 : if (buf[i] >= 'a' && buf[i] <= 'z') {
258 : 19 : buf[i] = static_cast<char>(buf[i] - ('a' - 'A'));
259 : : }
260 : : }
261 : : }
262 : :
263 : 73 : return size;
264 : : }
265 : : }
266 : :
267 : : char spec[16];
268 : 3 : char print_type = 'g';
269 : :
270 [ - + ]: 3 : switch (type) {
271 : 0 : case 'a':
272 : : case 'A':
273 : : case 'e':
274 : : case 'E':
275 : : case 'f':
276 : : case 'F':
277 : : case 'G':
278 : 0 : print_type = type;
279 : 0 : break;
280 : 3 : default:
281 : 3 : break;
282 : : }
283 : :
284 : 3 : const char* length_mod = std::is_same_v<FloatT, long double> ? "L" : ""; // NOLINT(google-runtime-float)
285 : :
286 [ - + ]: 3 : if (precision >= 0) {
287 [ # # ]: 0 : std::snprintf(spec, sizeof(spec), alt ? "%%#.%d%s%c" : "%%.%d%s%c", precision, length_mod, print_type);
288 : : } else {
289 [ + - ]: 3 : std::snprintf(spec, sizeof(spec), alt ? "%%#%s%c" : "%%%s%c", length_mod, print_type);
290 : : }
291 : :
292 : 3 : const int written = std::snprintf(buf, buflen, spec, value);
293 : :
294 [ + - + + : 3 : if VUNLIKELY (written < 0 || static_cast<size_t>(written) >= buflen) {
+ + ]
295 : 1 : return 0U;
296 : : }
297 : :
298 : 2 : auto size = static_cast<size_t>(written);
299 : :
300 [ + - - + ]: 2 : if (type == 'a' || type == 'A') {
301 [ # # # # ]: 0 : const size_t start = size > 0U && buf[0] == '-' ? 1U : 0U;
302 : :
303 [ # # # # : 0 : if (size >= start + 2U && buf[start] == '0' && (buf[start + 1U] == 'x' || buf[start + 1U] == 'X')) {
# # # # ]
304 : 0 : std::memmove(buf + start, buf + start + 2U, size - start - 2U);
305 : 0 : size -= 2U;
306 : : }
307 : : }
308 : :
309 : 2 : return size;
310 : : }
311 : :
312 : 71 : size_t format_floating_spec_to(char* buf, size_t buflen, double value, char type, int precision, bool alt) noexcept {
313 : 71 : return format_floating_spec_impl(buf, buflen, value, type, precision, alt);
314 : : }
315 : :
316 : : // NOLINTNEXTLINE(google-runtime-float)
317 : 7 : size_t format_floating_spec_to(char* buf, size_t buflen, long double value, char type, int precision,
318 : : bool alt) noexcept {
319 : 7 : return format_floating_spec_impl(buf, buflen, value, type, precision, alt);
320 : : }
321 : :
322 : 5 : uint64_t hash_combine(uint64_t a, uint64_t b) noexcept {
323 : 5 : a ^= b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2);
324 : 5 : return a;
325 : : }
326 : :
327 : 856 : void replace_string(std::string& str, const std::string& from, const std::string& to) noexcept {
328 [ + + + + : 856 : if (from.empty() || str.empty()) {
+ + ]
329 : 2 : return;
330 : : }
331 : :
332 : 854 : const bool from_aliased = &from == &str;
333 : 854 : const bool to_aliased = &to == &str;
334 [ - + ]: 854 : const std::string from_storage = from_aliased ? from : std::string();
335 [ + + ]: 854 : const std::string to_storage = to_aliased ? to : std::string();
336 [ - + ]: 854 : const std::string& from_ref = from_aliased ? from_storage : from;
337 [ + + ]: 854 : const std::string& to_ref = to_aliased ? to_storage : to;
338 : :
339 : 854 : size_t pos = 0;
340 : :
341 [ + + ]: 1709 : while ((pos = str.find(from_ref, pos)) != std::string::npos) {
342 : 855 : str.replace(pos, from_ref.length(), to_ref);
343 : 855 : pos += to_ref.length();
344 : : }
345 : 854 : }
346 : :
347 : 2247 : std::string trim_string(const std::string& str) noexcept {
348 [ + + ]: 2247 : if (str.empty()) {
349 : 136 : return {};
350 : : }
351 : :
352 : 2111 : auto start = str.begin();
353 : :
354 [ + + + + : 2125 : while (start != str.end() && std::isspace(static_cast<unsigned char>(*start)) != 0) {
+ + ]
355 : 14 : ++start;
356 : : }
357 : :
358 [ + + ]: 2111 : if VUNLIKELY (start == str.end()) {
359 : 1 : return {};
360 : : }
361 : :
362 : 2110 : auto end = str.rbegin();
363 : :
364 [ + - + + : 2119 : while (end != str.rend() && std::isspace(static_cast<unsigned char>(*end)) != 0) {
+ + ]
365 : 9 : ++end;
366 : : }
367 : :
368 : 2110 : return std::string(start, end.base());
369 : : }
370 : :
371 : 275 : std::string_view trim_string_view(std::string_view str) noexcept {
372 [ + + ]: 275 : if (str.empty()) {
373 : 1 : return {};
374 : : }
375 : :
376 : 274 : size_t start = 0;
377 : :
378 [ + + + + : 292 : while (start < str.size() && std::isspace(static_cast<unsigned char>(str[start])) != 0) {
+ + ]
379 : 18 : ++start;
380 : : }
381 : :
382 [ + + ]: 274 : if VUNLIKELY (start == str.size()) {
383 : 2 : return {};
384 : : }
385 : :
386 : 272 : size_t end = str.size();
387 : :
388 [ + - + + : 282 : while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1])) != 0) {
+ + ]
389 : 10 : --end;
390 : : }
391 : :
392 : 272 : return str.substr(start, end - start);
393 : : }
394 : :
395 : 12 : [[maybe_unused]] static size_t decode_utf8_code_point(const std::string& input, size_t index,
396 : : uint32_t& code_point) noexcept {
397 : 12 : const auto lead = static_cast<unsigned char>(input[index]);
398 : 12 : size_t extra = 0;
399 : :
400 [ + + ]: 12 : if (lead < 0x80U) {
401 : 8 : code_point = lead;
402 [ + + ]: 4 : } else if ((lead & 0xE0U) == 0xC0U) {
403 : 1 : code_point = lead & 0x1FU;
404 : 1 : extra = 1;
405 [ + + ]: 3 : } else if ((lead & 0xF0U) == 0xE0U) {
406 : 1 : code_point = lead & 0x0FU;
407 : 1 : extra = 2;
408 [ + - ]: 2 : } else if ((lead & 0xF8U) == 0xF0U) {
409 : 2 : code_point = lead & 0x07U;
410 : 2 : extra = 3;
411 : : } else {
412 : 0 : return 0;
413 : : }
414 : :
415 [ - + ]: 12 : if VUNLIKELY (index + extra >= input.size()) {
416 : 0 : return 0;
417 : : }
418 : :
419 [ + + ]: 21 : for (size_t offset = 1; offset <= extra; ++offset) {
420 : 9 : const auto unit = static_cast<unsigned char>(input[index + offset]);
421 : :
422 [ - + ]: 9 : if VUNLIKELY ((unit & 0xC0U) != 0x80U) {
423 : 0 : return 0;
424 : : }
425 : :
426 : 9 : code_point = (code_point << 6U) | (unit & 0x3FU);
427 : : }
428 : :
429 : : static constexpr uint32_t kMinCodePoint[] = {0U, 0x80U, 0x800U, 0x10000U};
430 : :
431 [ + + + + : 12 : if VUNLIKELY (code_point < kMinCodePoint[extra] || code_point > 0x10FFFFU ||
+ + + + +
+ + + +
+ ]
432 : : (code_point >= 0xD800U && code_point <= 0xDFFFU)) {
433 : 3 : return 0;
434 : : }
435 : :
436 : 9 : return extra + 1;
437 : : }
438 : :
439 : 8 : [[maybe_unused]] static void encode_utf8_code_point(uint32_t code_point, std::string& dest) noexcept {
440 [ + - ]: 8 : if (code_point < 0x80U) {
441 : 8 : dest.push_back(static_cast<char>(code_point));
442 [ # # ]: 0 : } else if (code_point < 0x800U) {
443 : 0 : dest.push_back(static_cast<char>(0xC0U | (code_point >> 6U)));
444 : 0 : dest.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
445 [ # # ]: 0 : } else if (code_point < 0x10000U) {
446 : 0 : dest.push_back(static_cast<char>(0xE0U | (code_point >> 12U)));
447 : 0 : dest.push_back(static_cast<char>(0x80U | ((code_point >> 6U) & 0x3FU)));
448 : 0 : dest.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
449 : : } else {
450 : 0 : dest.push_back(static_cast<char>(0xF0U | (code_point >> 18U)));
451 : 0 : dest.push_back(static_cast<char>(0x80U | ((code_point >> 12U) & 0x3FU)));
452 : 0 : dest.push_back(static_cast<char>(0x80U | ((code_point >> 6U) & 0x3FU)));
453 : 0 : dest.push_back(static_cast<char>(0x80U | (code_point & 0x3FU)));
454 : : }
455 : 8 : }
456 : :
457 : 7 : std::wstring string_to_wstring(const std::string& input) noexcept {
458 : : #ifdef _WIN32
459 : : std::wstring dest;
460 : : int length = ::MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(), nullptr, 0);
461 : :
462 : : if VUNLIKELY (length <= 0) {
463 : : return dest;
464 : : }
465 : :
466 : : auto* buffer = new (std::nothrow) WCHAR[length + 1];
467 : :
468 : : if VUNLIKELY (!buffer) {
469 : : return dest;
470 : : }
471 : :
472 : : ::MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(), buffer, length);
473 : :
474 : : buffer[length] = '\0';
475 : : dest.append(buffer, length);
476 : : delete[] buffer;
477 : :
478 : : return dest;
479 : : #else
480 : 7 : std::wstring dest;
481 : :
482 : 7 : dest.reserve(input.size());
483 : :
484 : 7 : size_t index = 0;
485 : :
486 [ + + ]: 16 : while (index < input.size()) {
487 : 12 : uint32_t code_point = 0;
488 : 12 : const size_t consumed = decode_utf8_code_point(input, index, code_point);
489 : :
490 [ + + ]: 12 : if VUNLIKELY (consumed == 0) {
491 : 3 : return std::wstring();
492 : : }
493 : :
494 : 9 : dest.push_back(static_cast<wchar_t>(code_point));
495 : 9 : index += consumed;
496 : : }
497 : :
498 : 4 : return dest;
499 : : #endif
500 : 7 : }
501 : :
502 : 4 : std::string wstring_to_string(const std::wstring& input) noexcept {
503 : : #ifdef _WIN32
504 : : std::string dest;
505 : : int length = ::WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(), nullptr, 0, nullptr, nullptr);
506 : :
507 : : if VUNLIKELY (length <= 0) {
508 : : return dest;
509 : : }
510 : :
511 : : auto* buffer = new (std::nothrow) char[length + 1];
512 : :
513 : : if VUNLIKELY (!buffer) {
514 : : return dest;
515 : : }
516 : :
517 : : ::WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(), buffer, length, nullptr, nullptr);
518 : :
519 : : buffer[length] = '\0';
520 : : dest.append(buffer, length);
521 : : delete[] buffer;
522 : :
523 : : return dest;
524 : : #else
525 : 4 : std::string dest;
526 : :
527 : 4 : dest.reserve(input.size());
528 : :
529 [ + + ]: 12 : for (const wchar_t unit : input) {
530 : 9 : const auto code_point = static_cast<uint32_t>(static_cast<std::make_unsigned_t<wchar_t>>(unit));
531 : :
532 [ + - + + : 9 : if VUNLIKELY (code_point > 0x10FFFFU || (code_point >= 0xD800U && code_point <= 0xDFFFU)) {
+ - + + +
+ ]
533 : 1 : return std::string();
534 : : }
535 : :
536 : 8 : encode_utf8_code_point(code_point, dest);
537 : : }
538 : :
539 : 3 : return dest;
540 : : #endif
541 : 4 : }
542 : :
543 : 2 : std::string string_local_to_utf8(const std::string& local_str) noexcept {
544 : : #ifdef _WIN32
545 : :
546 : : if VUNLIKELY (local_str.empty()) {
547 : : return {};
548 : : }
549 : :
550 : : int wide_size = MultiByteToWideChar(CP_ACP, 0, local_str.c_str(), -1, nullptr, 0);
551 : :
552 : : if VUNLIKELY (wide_size <= 0) {
553 : : return {};
554 : : }
555 : :
556 : : std::wstring wide_str(wide_size, L'\0');
557 : : MultiByteToWideChar(CP_ACP, 0, local_str.c_str(), -1, wide_str.data(), wide_size);
558 : :
559 : : int utf8_size = WideCharToMultiByte(CP_UTF8, 0, wide_str.c_str(), -1, nullptr, 0, nullptr, nullptr);
560 : :
561 : : if VUNLIKELY (utf8_size <= 0) {
562 : : return {};
563 : : }
564 : :
565 : : std::string utf8_str(utf8_size - 1, '\0');
566 : : WideCharToMultiByte(CP_UTF8, 0, wide_str.c_str(), -1, utf8_str.data(), utf8_size, nullptr, nullptr);
567 : :
568 : : return utf8_str;
569 : : #else
570 : 2 : return local_str;
571 : : #endif
572 : : }
573 : :
574 : 2 : std::string string_utf8_to_local(const std::string& utf8_str) noexcept {
575 : : #ifdef _WIN32
576 : :
577 : : if VUNLIKELY (utf8_str.empty()) {
578 : : return {};
579 : : }
580 : :
581 : : int wide_size = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, nullptr, 0);
582 : :
583 : : if VUNLIKELY (wide_size <= 0) {
584 : : return {};
585 : : }
586 : :
587 : : std::wstring wide_str(wide_size, L'\0');
588 : : MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), -1, wide_str.data(), wide_size);
589 : :
590 : : int local_size = WideCharToMultiByte(CP_ACP, 0, wide_str.c_str(), -1, nullptr, 0, nullptr, nullptr);
591 : :
592 : : if VUNLIKELY (local_size <= 0) {
593 : : return {};
594 : : }
595 : :
596 : : std::string local_str(local_size - 1, '\0');
597 : : WideCharToMultiByte(CP_ACP, 0, wide_str.c_str(), -1, local_str.data(), local_size, nullptr, nullptr);
598 : :
599 : : return local_str;
600 : : #else
601 : 2 : return utf8_str;
602 : : #endif
603 : : }
604 : :
605 : 2 : std::string path_to_string(const std::filesystem::path& path) noexcept {
606 : : try {
607 : : #ifdef _WIN32
608 : : #if __cplusplus >= 202002L
609 : : auto u8str = path.u8string();
610 : : return std::string(u8str.begin(), u8str.end());
611 : : #else
612 : : return path.u8string();
613 : : #endif
614 : : #else
615 [ + - ]: 2 : return path.string();
616 : : #endif
617 : : } catch (std::filesystem::filesystem_error&) { // LCOV_EXCL_LINE GCOVR_EXCL_LINE
618 : : return std::string(); // LCOV_EXCL_LINE GCOVR_EXCL_LINE
619 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
620 : : }
621 : :
622 : 10 : std::vector<std::string> split(const std::string& str, char f) noexcept {
623 [ + + ]: 10 : if (str.empty()) {
624 : 1 : return {};
625 : : }
626 : :
627 : 9 : std::vector<std::string> result;
628 : :
629 : 9 : size_t start = 0;
630 : 9 : size_t end = 0;
631 : :
632 [ + + ]: 22 : while ((end = str.find(f, start)) != std::string::npos) {
633 [ + + ]: 13 : if (end > start) {
634 : 7 : result.emplace_back(str.substr(start, end - start));
635 : : }
636 : :
637 : 13 : start = end + 1;
638 : : }
639 : :
640 [ + + ]: 9 : if (start < str.size()) {
641 : 6 : result.emplace_back(str.substr(start));
642 : : }
643 : :
644 : 9 : return result;
645 : 9 : }
646 : :
647 : 622 : std::vector<std::string_view> split_view(std::string_view str, char f) noexcept {
648 [ + + ]: 622 : if (str.empty()) {
649 : 1 : return {};
650 : : }
651 : :
652 : 621 : std::vector<std::string_view> result;
653 : :
654 : 621 : size_t start = 0;
655 : 621 : size_t end = 0;
656 : :
657 [ + + ]: 2141 : while ((end = str.find(f, start)) != std::string_view::npos) {
658 [ + + ]: 1520 : if (end > start) {
659 : 1519 : result.emplace_back(str.substr(start, end - start));
660 : : }
661 : :
662 : 1520 : start = end + 1;
663 : : }
664 : :
665 [ + + ]: 621 : if (start < str.size()) {
666 : 436 : result.emplace_back(str.substr(start));
667 : : }
668 : :
669 : 621 : return result;
670 : 621 : }
671 : :
672 : 474 : std::vector<std::string> split_any(const std::string& str, std::string_view delimiters) noexcept {
673 : 474 : const auto views = split_any_view(str, delimiters);
674 : :
675 : 474 : std::vector<std::string> result;
676 : 474 : result.reserve(views.size());
677 : :
678 [ + + ]: 732 : for (const auto item : views) {
679 : 258 : result.emplace_back(item);
680 : : }
681 : :
682 : 474 : return result;
683 : 474 : }
684 : :
685 : 480 : std::vector<std::string_view> split_any_view(std::string_view str, std::string_view delimiters) noexcept {
686 [ + + ]: 480 : if (str.empty()) {
687 : 225 : return {};
688 : : }
689 : :
690 [ + + ]: 255 : if (delimiters.empty()) {
691 : 2 : auto token = trim_string_view(str);
692 : :
693 [ - + ]: 2 : if (token.empty()) {
694 : 0 : return {};
695 : : }
696 : :
697 : 2 : return {token};
698 : : }
699 : :
700 : 253 : std::vector<std::string_view> result;
701 : :
702 : 253 : size_t start = 0;
703 : 253 : size_t end = 0;
704 : :
705 [ + + ]: 281 : while ((end = str.find_first_of(delimiters, start)) != std::string_view::npos) {
706 [ + + ]: 28 : if (end > start) {
707 : 20 : auto token = trim_string_view(str.substr(start, end - start));
708 : :
709 [ + + ]: 20 : if (!token.empty()) {
710 : 19 : result.emplace_back(token);
711 : : }
712 : : }
713 : :
714 : 28 : start = end + 1;
715 : : }
716 : :
717 [ + + ]: 253 : if (start < str.size()) {
718 : 248 : auto token = trim_string_view(str.substr(start));
719 : :
720 [ + - ]: 248 : if (!token.empty()) {
721 : 248 : result.emplace_back(token);
722 : : }
723 : : }
724 : :
725 : 253 : return result;
726 : 253 : }
727 : :
728 : 1733 : std::string escape_field(std::string_view value) noexcept {
729 : : static constexpr char kHex[] = "0123456789ABCDEF";
730 : :
731 : 1733 : std::string out;
732 : 1733 : out.reserve(value.size());
733 : :
734 [ + + ]: 35160 : for (const unsigned char ch : value) {
735 [ + + + + : 33427 : if (ch == '%' || ch == ' ' || ch == ':' || ch == '\n' || ch == '\r') {
+ + + + +
+ ]
736 : 999 : out.push_back('%');
737 : 999 : out.push_back(kHex[(ch >> 4U) & 0x0FU]);
738 : 999 : out.push_back(kHex[ch & 0x0FU]);
739 : : } else {
740 : 32428 : out.push_back(static_cast<char>(ch));
741 : : }
742 : : }
743 : :
744 : 1733 : return out;
745 : : }
746 : :
747 : 867 : std::string unescape_field(std::string_view value) noexcept {
748 : 536 : auto from_hex = [](char ch) noexcept -> int {
749 [ + + + + ]: 536 : if (ch >= '0' && ch <= '9') {
750 : 271 : return ch - '0';
751 : : }
752 : :
753 [ + + + + ]: 265 : if (ch >= 'A' && ch <= 'F') {
754 : 261 : return ch - 'A' + 10;
755 : : }
756 : :
757 [ + + + - ]: 4 : if (ch >= 'a' && ch <= 'f') {
758 : 1 : return ch - 'a' + 10;
759 : : }
760 : :
761 : 3 : return -1;
762 : : };
763 : :
764 : 867 : std::string out;
765 : 867 : out.reserve(value.size());
766 : :
767 [ + + ]: 14011 : for (size_t i = 0; i < value.size(); ++i) {
768 [ + + + + : 13144 : if (value[i] == '%' && i + 2U < value.size()) {
+ + ]
769 : 268 : const int hi = from_hex(value[i + 1U]);
770 : 268 : const int lo = from_hex(value[i + 2U]);
771 : :
772 [ + + + + : 268 : if VLIKELY (hi >= 0 && lo >= 0) {
+ + ]
773 : 266 : out.push_back(static_cast<char>((hi << 4U) | lo));
774 : 266 : i += 2U;
775 : :
776 : 266 : continue;
777 : : }
778 : : }
779 : :
780 : 12878 : out.push_back(value[i]);
781 : : }
782 : :
783 : 867 : return out;
784 : : }
785 : :
786 : 684 : uint32_t get_hash_code(const std::string& str) noexcept {
787 [ + + ]: 684 : if (str.empty()) {
788 : 364 : return 0;
789 : : }
790 : :
791 : 320 : uint32_t value = 0;
792 : :
793 : 320 : auto [p, error] = std::from_chars(str.data(), str.data() + str.size(), value);
794 : :
795 [ + + + + : 320 : if (error == std::errc() && p == str.data() + str.size()) {
+ + ]
796 : 1 : return value;
797 : : }
798 : :
799 : 319 : value = static_cast<uint32_t>(std::hash<std::string>{}(str));
800 : :
801 : 319 : return value;
802 : : }
803 : :
804 : 6 : std::string format_milliseconds(int64_t milliseconds, bool show_millis) noexcept {
805 : : char buffer[32];
806 : :
807 [ + + ]: 6 : if (milliseconds < 0) {
808 : 1 : constexpr int64_t kMillisecondsPerDay = 24LL * 60LL * 60LL * 1000LL;
809 : :
810 : 1 : milliseconds %= kMillisecondsPerDay;
811 : :
812 [ + - ]: 1 : if (milliseconds < 0) {
813 : 1 : milliseconds += kMillisecondsPerDay;
814 : : }
815 : : }
816 : :
817 : 6 : int64_t hours = milliseconds / (1000 * 60 * 60);
818 : 6 : int64_t minutes = (milliseconds % (1000 * 60 * 60)) / (1000 * 60);
819 : 6 : int64_t seconds = ((milliseconds % (1000 * 60 * 60)) % (1000 * 60)) / 1000;
820 : :
821 : : // NOLINTBEGIN
822 : :
823 [ + + ]: 6 : if (show_millis) {
824 : 3 : int64_t millis = ((milliseconds % (1000 * 60 * 60)) % (1000 * 60)) % 1000;
825 : :
826 : 3 : std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld:%02lld:%03lld", static_cast<long long>(hours),
827 : : static_cast<long long>(minutes), static_cast<long long>(seconds), static_cast<long long>(millis));
828 : :
829 : : } else {
830 : 3 : std::snprintf(buffer, sizeof(buffer), "%02lld:%02lld:%02lld", static_cast<long long>(hours),
831 : : static_cast<long long>(minutes), static_cast<long long>(seconds));
832 : : }
833 : :
834 : : // NOLINTEND
835 : :
836 : 6 : return buffer;
837 : : }
838 : :
839 : 6 : std::string format_date(int64_t nanoseconds_since_epoch) noexcept {
840 : : auto time_point = std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds>(
841 : 6 : std::chrono::nanoseconds(nanoseconds_since_epoch));
842 : :
843 : 6 : auto system_time = std::chrono::floor<std::chrono::milliseconds>(time_point);
844 : 6 : auto seconds = std::chrono::floor<std::chrono::seconds>(system_time);
845 : 6 : auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(system_time - seconds).count();
846 : :
847 : 6 : std::time_t time_t_seconds = seconds.time_since_epoch().count();
848 : 6 : std::tm tm_result{};
849 : 6 : std::tm* tm_ptr = nullptr;
850 : :
851 : : #if defined(_WIN32) || defined(_WIN64)
852 : :
853 : : if (gmtime_s(&tm_result, &time_t_seconds) == 0) {
854 : : tm_ptr = &tm_result;
855 : : }
856 : : #else
857 : :
858 [ + - ]: 6 : if (gmtime_r(&time_t_seconds, &tm_result) != nullptr) {
859 : 6 : tm_ptr = &tm_result;
860 : : }
861 : : #endif
862 : :
863 [ - + ]: 6 : if VUNLIKELY (!tm_ptr) {
864 : : return {}; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
865 : : }
866 : :
867 : : char buffer[32];
868 : 6 : std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_ptr);
869 : :
870 : : char full_buffer[64];
871 : 6 : std::snprintf(full_buffer, sizeof(full_buffer), "%s.%03d", buffer, static_cast<int>(milliseconds));
872 : :
873 : 6 : return full_buffer;
874 : : }
875 : :
876 : 5 : std::string format_time_diff(int32_t milliseconds) noexcept {
877 : 5 : bool negative = milliseconds < 0;
878 : :
879 [ + + ]: 5 : int64_t abs_milliseconds = negative ? -static_cast<int64_t>(milliseconds) : static_cast<int64_t>(milliseconds);
880 : :
881 : 5 : int hours = static_cast<int>(abs_milliseconds / (1000 * 60 * 60));
882 : 5 : int minutes = static_cast<int>((abs_milliseconds % (1000 * 60 * 60)) / (1000 * 60));
883 : 5 : int seconds = static_cast<int>((abs_milliseconds % (1000 * 60)) / 1000);
884 : 5 : int millis = static_cast<int>(abs_milliseconds % 1000);
885 : :
886 : : char buffer[32];
887 : :
888 [ + + ]: 5 : if (negative) {
889 : 1 : std::snprintf(buffer, sizeof(buffer), "-%02d:%02d:%02d:%03d", hours, minutes, seconds, millis);
890 : : } else {
891 : 4 : std::snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d:%03d", hours, minutes, seconds, millis);
892 : : }
893 : :
894 : 5 : return buffer;
895 : : }
896 : :
897 : 4 : std::string format_hex_number(int64_t hex_number) noexcept {
898 : 4 : return format_hex_number(static_cast<uint64_t>(hex_number));
899 : : }
900 : :
901 : 6 : std::string format_hex_number(uint64_t hex_number) noexcept {
902 : : char buffer[32];
903 : 6 : auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), hex_number, 16);
904 : :
905 [ + - ]: 6 : if (ec == std::errc()) {
906 : 6 : std::string result = "0x";
907 : :
908 [ + + ]: 33 : for (char* p = buffer; p < ptr; ++p) {
909 : 27 : result += static_cast<char>(std::toupper(*p)); // NOLINT(clang-analyzer-core.CallAndMessage)
910 : : }
911 : :
912 : 6 : return result;
913 : : } // LCOV_EXCL_LINE GCOVR_EXCL_LINE
914 : :
915 : : return "0x0"; // LCOV_EXCL_LINE GCOVR_EXCL_LINE
916 : : }
917 : :
918 : 7 : std::string format_file_size(size_t size) noexcept {
919 : : char buffer[32];
920 : :
921 [ + + ]: 7 : if (size < 1024LL * 1024) {
922 : 4 : std::snprintf(buffer, sizeof(buffer), "%.2fKB", size / 1024.0);
923 [ + + ]: 3 : } else if (size < 1024LL * 1024 * 1024) {
924 : 2 : std::snprintf(buffer, sizeof(buffer), "%.2fMB", size / 1024.0 / 1024.0);
925 : : } else {
926 : 1 : std::snprintf(buffer, sizeof(buffer), "%.2fGB", size / 1024.0 / 1024.0 / 1024.0);
927 : : }
928 : :
929 : 7 : return buffer;
930 : : }
931 : :
932 : 5 : std::string format_rate_size(size_t size) noexcept {
933 : : char buffer[32];
934 : :
935 [ + + ]: 5 : if (size < 1024) {
936 : 2 : std::snprintf(buffer, sizeof(buffer), "%.2fB/s", static_cast<double>(size));
937 [ + + ]: 3 : } else if (size < 1024LL * 1024) {
938 : 1 : std::snprintf(buffer, sizeof(buffer), "%.2fKB/s", size / 1024.0);
939 [ + + ]: 2 : } else if (size < 1024LL * 1024 * 1024) {
940 : 1 : std::snprintf(buffer, sizeof(buffer), "%.2fMB/s", size / 1024.0 / 1024.0);
941 : : } else {
942 : 1 : std::snprintf(buffer, sizeof(buffer), "%.2fGB/s", size / 1024.0 / 1024.0 / 1024.0);
943 : : }
944 : :
945 : 5 : return buffer;
946 : : }
947 : :
948 : 13 : int64_t convert_date_to_timestamp(const std::string& date) noexcept {
949 : 13 : std::tm tm = {};
950 : : char delimiter_ms;
951 : 13 : int milliseconds = 0;
952 : :
953 [ + + ]: 13 : thread_local std::istringstream ss;
954 : 13 : ss.clear();
955 : 13 : ss.str(date);
956 : :
957 : 13 : ss >> std::get_time(&tm, "%Y/%m/%d %H:%M:%S");
958 : :
959 [ + + ]: 13 : if (ss.fail()) {
960 : 2 : return -1;
961 : : }
962 : :
963 : 11 : ss >> std::ws;
964 : :
965 [ + + ]: 11 : if (!ss.eof()) {
966 [ + + + + : 9 : if (!(ss >> delimiter_ms >> milliseconds) || delimiter_ms != ':' || milliseconds < 0 || milliseconds >= 1000) {
+ - + + +
+ ]
967 : 3 : return -1;
968 : : }
969 : :
970 : 6 : ss >> std::ws;
971 : :
972 [ + + ]: 6 : if (!ss.eof()) {
973 : 1 : return -1;
974 : : }
975 : : }
976 : :
977 : 7 : tm.tm_isdst = -1;
978 : :
979 : 7 : auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));
980 : 7 : auto ns = std::chrono::time_point_cast<std::chrono::nanoseconds>(tp).time_since_epoch().count();
981 : :
982 : 7 : return ns + (static_cast<int64_t>(milliseconds) * 1'000'000LL);
983 : : }
984 : :
985 : : } // namespace Helpers
986 : :
987 : : } // namespace vlink
|