VLink  2.0.0
A high-performance communication middleware
helpers.h
Go to the documentation of this file.
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 /**
25  * @file helpers.h
26  * @brief Stateless string, number, hash and formatting helpers used throughout VLink.
27  *
28  * @details
29  * Functions in this header are portable, allocation-free where possible, and never throw; bad
30  * input is reported via empty / default return values. Encoding conversion routines fall back
31  * to a pass-through on POSIX where the input is assumed to already be UTF-8.
32  *
33  * @par Helper categories
34  *
35  * | Category | Representative entry points |
36  * | ----------------- | -------------------------------------------------------------------------------------------- |
37  * | Number parsing | @c to_int, @c to_long, @c double_to_string |
38  * | Hashing | @c hash_combine, @c get_hash_code |
39  * | String editing | @c replace_string, @c trim_string, @c trim_string_view |
40  * | Wide / locale | @c string_to_wstring, @c wstring_to_string, @c string_local_to_utf8, @c string_utf8_to_local |
41  * | Filesystem | @c path_to_string |
42  * | Splitting | @c split, @c split_view, @c split_any, @c split_any_view |
43  * | Field encoding | @c escape_field, @c unescape_field |
44  * | Time formatting | @c format_milliseconds, @c format_date, @c format_time_diff |
45  * | Number formatting | @c format_hex_number, @c format_file_size, @c format_rate_size |
46  * | Date parsing | @c convert_date_to_timestamp |
47  * | Prefix / suffix | @c has_startwith, @c has_endwith, @c contains_substring |
48  *
49  * @par Example
50  * @code
51  * auto parts = vlink::Helpers::split("a,b,c", ','); // {"a", "b", "c"}
52  * std::string size = vlink::Helpers::format_file_size(1536); // "1.50KB"
53  * bool startsWith = vlink::Helpers::has_startwith("vlink://hello", "vlink://");
54  * @endcode
55  */
56 
57 #pragma once
58 
59 #include <cstdint>
60 #include <filesystem>
61 #include <string>
62 #include <string_view>
63 #include <vector>
64 
65 #include "./macros.h"
66 
67 namespace vlink {
68 
69 /**
70  * @namespace vlink::Helpers
71  * @brief Stateless utility functions shared across VLink subsystems.
72  */
73 namespace Helpers { // NOLINT(readability-identifier-naming)
74 
75 /**
76  * @brief Parses an integer string with auto base detection (@c 0x -> hex, leading @c 0 -> octal,
77  * otherwise decimal).
78  *
79  * @param str Source string.
80  * @param dv Default value returned on parse failure. Default: @c 0.
81  * @return Parsed @c int or @p dv on failure.
82  */
83 [[nodiscard]] VLINK_EXPORT int to_int(const std::string& str, int dv = 0) noexcept;
84 
85 /**
86  * @brief Parses a 64-bit integer string with optional trailing-character allowance.
87  *
88  * @details
89  * Calls @c std::stoll with auto base detection (@c 0x -> hex, leading @c 0 -> octal, otherwise
90  * decimal). Parsing always starts at index @c 0; @p offset is the number of trailing characters
91  * the parser may leave unconsumed (for example to skip a unit suffix such as @c "100s" with
92  * @p offset @c == @c 1). Mismatched consumption returns @p dv.
93  *
94  * @param str Source string.
95  * @param dv Default value on failure. Default: @c 0.
96  * @param offset Number of trailing characters allowed to remain unconsumed. Default: @c 0.
97  * @return Parsed @c int64_t or @p dv on failure.
98  */
99 [[nodiscard]] VLINK_EXPORT int64_t to_long(const std::string& str, int64_t dv = 0, int offset = 0) noexcept;
100 
101 /**
102  * @brief Formats a @c double with the requested decimal precision.
103  *
104  * @param value Source value.
105  * @param precision Number of digits after the decimal point. Default: @c 2.
106  * @return Decimal string representation.
107  */
108 [[nodiscard]] VLINK_EXPORT std::string double_to_string(double value, int precision = 2) noexcept;
109 
110 /**
111  * @brief Combines two 64-bit hash values into one via a Murmur-style mix.
112  *
113  * @param a First hash value.
114  * @param b Second hash value.
115  * @return Mixed hash value.
116  */
117 [[nodiscard]] VLINK_EXPORT uint64_t hash_combine(uint64_t a, uint64_t b) noexcept;
118 
119 /**
120  * @brief Replaces all occurrences of @p from in @p str with @p to in place.
121  *
122  * @param str String mutated in place.
123  * @param from Substring to search for.
124  * @param to Replacement substring.
125  */
126 VLINK_EXPORT void replace_string(std::string& str, const std::string& from, const std::string& to) noexcept;
127 
128 /**
129  * @brief Returns @p str with leading and trailing whitespace removed.
130  *
131  * @param str Source string.
132  * @return Trimmed copy.
133  */
134 [[nodiscard]] VLINK_EXPORT std::string trim_string(const std::string& str) noexcept;
135 
136 /**
137  * @brief Trims whitespace from both sides of a @c string_view without allocating.
138  *
139  * @details
140  * The returned view references the same storage as @p str; callers must keep
141  * the original character storage alive while using the result.
142  *
143  * @param str Source view.
144  * @return Trimmed view, or an empty view when @p str is empty or all whitespace.
145  */
146 [[nodiscard]] VLINK_EXPORT std::string_view trim_string_view(std::string_view str) noexcept;
147 
148 /**
149  * @brief Converts UTF-8 to a wide-character string.
150  *
151  * @param input UTF-8 source.
152  * @return Wide-character result; empty on failure.
153  */
154 [[nodiscard]] VLINK_EXPORT std::wstring string_to_wstring(const std::string& input) noexcept;
155 
156 /**
157  * @brief Converts a wide-character string to UTF-8.
158  *
159  * @param input Wide source.
160  * @return UTF-8 result; empty on failure.
161  */
162 [[nodiscard]] VLINK_EXPORT std::string wstring_to_string(const std::wstring& input) noexcept;
163 
164 /**
165  * @brief Converts a system-locale string to UTF-8.
166  *
167  * @details
168  * Performs Windows ANSI-code-page conversion; returns the input unchanged on POSIX.
169  *
170  * @param local_str Locally encoded string.
171  * @return UTF-8 string, or the input when no conversion is needed.
172  */
173 [[nodiscard]] VLINK_EXPORT std::string string_local_to_utf8(const std::string& local_str) noexcept;
174 
175 /**
176  * @brief Converts UTF-8 to the active system locale on Windows; pass-through on POSIX.
177  *
178  * @param utf8_str UTF-8 source.
179  * @return Locally encoded string.
180  */
181 [[nodiscard]] VLINK_EXPORT std::string string_utf8_to_local(const std::string& utf8_str) noexcept;
182 
183 /**
184  * @brief Converts a filesystem path to a portable UTF-8 string.
185  *
186  * @param path Source path.
187  * @return UTF-8 path string.
188  */
189 [[nodiscard]] VLINK_EXPORT std::string path_to_string(const std::filesystem::path& path) noexcept;
190 
191 /**
192  * @brief Splits a string by a single-character delimiter; empty parts are skipped.
193  *
194  * @param str Source string.
195  * @param f Delimiter character. Defaults to @c ','.
196  * @return Vector of non-empty substrings.
197  */
198 [[nodiscard]] VLINK_EXPORT std::vector<std::string> split(const std::string& str, char f = ',') noexcept;
199 
200 /**
201  * @brief Splits a @c string_view by a delimiter and returns non-owning views.
202  *
203  * @details
204  * The caller must keep @p str alive while the returned views are used.
205  *
206  * @param str Source view.
207  * @param f Delimiter character. Defaults to @c ','.
208  * @return Vector of non-empty views over the parts.
209  */
210 [[nodiscard]] VLINK_EXPORT std::vector<std::string_view> split_view(std::string_view str, char f = ',') noexcept;
211 
212 /**
213  * @brief Splits a string by any delimiter in @p delimiters; each token is trimmed and empty tokens are skipped.
214  *
215  * @param str Source string.
216  * @param delimiters Delimiter character set. Defaults to @c " ,".
217  * @return Vector of trimmed, non-empty tokens.
218  */
219 [[nodiscard]] VLINK_EXPORT std::vector<std::string> split_any(const std::string& str,
220  std::string_view delimiters = " ,") noexcept;
221 
222 /**
223  * @brief Splits a view by any delimiter in @p delimiters and returns non-owning trimmed views.
224  *
225  * @details
226  * The caller must keep @p str alive while the returned views are used.
227  *
228  * @param str Source view.
229  * @param delimiters Delimiter character set. Defaults to @c " ,".
230  * @return Vector of trimmed, non-empty views over the tokens.
231  */
232 [[nodiscard]] VLINK_EXPORT std::vector<std::string_view> split_any_view(std::string_view str,
233  std::string_view delimiters = " ,") noexcept;
234 
235 /**
236  * @brief Percent-encodes one field of a delimiter-separated text frame.
237  *
238  * @details
239  * Encodes @c %, space, @c :, LF and CR as uppercase @c %XX sequences; other bytes pass through.
240  *
241  * @param value Field to encode.
242  * @return Encoded field.
243  */
244 [[nodiscard]] VLINK_EXPORT std::string escape_field(std::string_view value) noexcept;
245 
246 /**
247  * @brief Reverses @c escape_field encoding.
248  *
249  * @details
250  * Decodes valid @c %XX sequences; preserves invalid or truncated sequences verbatim so legacy
251  * frames remain parseable.
252  *
253  * @param value Encoded field.
254  * @return Decoded field.
255  */
256 [[nodiscard]] VLINK_EXPORT std::string unescape_field(std::string_view value) noexcept;
257 
258 /**
259  * @brief Maps a string to a 32-bit code.
260  *
261  * @details
262  * If @p str parses as an integer it is returned as its numeric value; otherwise the result is
263  * @c std::hash<std::string> truncated to 32 bits. An empty string yields @c 0.
264  *
265  * @param str Source string.
266  * @return 32-bit code.
267  */
268 [[nodiscard]] VLINK_EXPORT uint32_t get_hash_code(const std::string& str) noexcept;
269 
270 /**
271  * @brief Renders a millisecond duration as a human-readable time string.
272  *
273  * @param milliseconds Source duration.
274  * @param show_millis When @c true the millisecond field is appended.
275  * @return Formatted duration string.
276  */
277 [[nodiscard]] VLINK_EXPORT std::string format_milliseconds(int64_t milliseconds, bool show_millis) noexcept;
278 
279 /**
280  * @brief Renders a nanosecond Unix timestamp as @c "YYYY-MM-DD @c HH:MM:SS.mmm" in UTC.
281  *
282  * @param nanoseconds_since_epoch Source timestamp.
283  * @return Formatted date string.
284  */
285 [[nodiscard]] VLINK_EXPORT std::string format_date(int64_t nanoseconds_since_epoch) noexcept;
286 
287 /**
288  * @brief Renders a millisecond time delta as @c "HH:MM:SS:mmm".
289  *
290  * @param milliseconds Source delta.
291  * @return Formatted delta string.
292  */
293 [[nodiscard]] VLINK_EXPORT std::string format_time_diff(int32_t milliseconds) noexcept;
294 
295 /**
296  * @brief Renders a signed 64-bit integer as a @c "0x..." hex literal.
297  *
298  * @param hex_number Source value.
299  * @return Hex literal string.
300  */
301 [[nodiscard]] VLINK_EXPORT std::string format_hex_number(int64_t hex_number) noexcept;
302 
303 /**
304  * @brief Renders an unsigned 64-bit integer as a @c "0x..." hex literal.
305  *
306  * @param hex_number Source value.
307  * @return Hex literal string.
308  */
309 [[nodiscard]] VLINK_EXPORT std::string format_hex_number(uint64_t hex_number) noexcept;
310 
311 /**
312  * @brief Renders a byte count as a KB / MB / GB string with two decimal places.
313  *
314  * @param size Source value in bytes.
315  * @return Human-readable size string.
316  */
317 [[nodiscard]] VLINK_EXPORT std::string format_file_size(size_t size) noexcept;
318 
319 /**
320  * @brief Renders a byte-per-second rate as a B/s, KB/s, MB/s or GB/s string.
321  *
322  * @param size Source rate in bytes per second.
323  * @return Human-readable rate string.
324  */
325 [[nodiscard]] VLINK_EXPORT std::string format_rate_size(size_t size) noexcept;
326 
327 /**
328  * @brief Parses a @c "%Y/%m/%d @c %H:%M:%S" (optionally @c ":<ms>") date into a Unix nanosecond timestamp.
329  *
330  * @details
331  * Interpreted in the local timezone via @c std::mktime. ISO-8601 dashes are not accepted.
332  *
333  * @param date Source date string.
334  * @return Nanoseconds since Unix epoch, or @c -1 on parse failure.
335  */
336 [[nodiscard]] VLINK_EXPORT int64_t convert_date_to_timestamp(const std::string& date) noexcept;
337 
338 /**
339  * @brief Compile-time prefix check against a string literal.
340  *
341  * @tparam SizeT Size of @p target including the null terminator (deduced).
342  * @param str String to test.
343  * @param target Literal prefix.
344  * @return @c true when @p str starts with @p target.
345  */
346 template <uint8_t SizeT>
347 [[nodiscard]] bool has_startwith(const std::string& str, const char (&target)[SizeT]) noexcept;
348 
349 /**
350  * @brief Compile-time suffix check against a string literal.
351  *
352  * @tparam SizeT Size of @p target including the null terminator (deduced).
353  * @param str String to test.
354  * @param target Literal suffix.
355  * @return @c true when @p str ends with @p target.
356  */
357 template <uint8_t SizeT>
358 [[nodiscard]] bool has_endwith(const std::string& str, const char (&target)[SizeT]) noexcept;
359 
360 /**
361  * @brief Constexpr prefix check for two @c string_view values.
362  *
363  * @param str String to test.
364  * @param target Prefix to check for.
365  * @return @c true when @p str starts with @p target.
366  */
367 [[nodiscard]] constexpr bool has_startwith(std::string_view str, std::string_view target) noexcept;
368 
369 /**
370  * @brief Constexpr suffix check for two @c string_view values.
371  *
372  * @param str String to test.
373  * @param target Suffix to check for.
374  * @return @c true when @p str ends with @p target.
375  */
376 [[nodiscard]] constexpr bool has_endwith(std::string_view str, std::string_view target) noexcept;
377 
378 /**
379  * @brief Constexpr substring search.
380  *
381  * @param sv String to search.
382  * @param needle Substring to look for; an empty needle returns @c true.
383  * @return @c true when @p sv contains @p needle.
384  */
385 [[nodiscard]] constexpr bool contains_substring(std::string_view sv, std::string_view needle) noexcept;
386 
387 ////////////////////////////////////////////////////////////////
388 /// Details
389 ////////////////////////////////////////////////////////////////
390 
391 template <uint8_t SizeT>
392 inline bool has_startwith(const std::string& str, const char (&target)[SizeT]) noexcept {
393  if constexpr (SizeT == 0) {
394  (void)str;
395  return false;
396  }
397 
398  return str.compare(0, SizeT - 1, target) == 0;
399 }
400 
401 template <uint8_t SizeT>
402 inline bool has_endwith(const std::string& str, const char (&target)[SizeT]) noexcept {
403  if constexpr (SizeT == 0) {
404  (void)str;
405  return false;
406  }
407 
408  if VUNLIKELY (str.size() < SizeT - 1) {
409  return false;
410  }
411 
412  return str.compare(str.size() - (SizeT - 1), SizeT - 1, target) == 0;
413 }
414 
415 inline constexpr bool has_startwith(std::string_view str, std::string_view target) noexcept {
416 #if __cplusplus >= 202002L
417  return str.starts_with(target);
418 #else
419  return target.size() <= str.size() && str.substr(0, target.size()) == target;
420 #endif
421 }
422 
423 inline constexpr bool has_endwith(std::string_view str, std::string_view target) noexcept {
424 #if __cplusplus >= 202002L
425  return str.ends_with(target);
426 #else
427  return target.size() <= str.size() && str.substr(str.size() - target.size(), target.size()) == target;
428 #endif
429 }
430 
431 inline constexpr bool contains_substring(std::string_view sv, std::string_view needle) noexcept {
432  if VUNLIKELY (needle.empty()) {
433  return true;
434  }
435 
436  if VUNLIKELY (sv.size() < needle.size()) {
437  return false;
438  }
439 
440  for (size_t i = 0; i <= sv.size() - needle.size(); ++i) {
441  bool match = true;
442 
443  for (size_t j = 0; j < needle.size(); ++j) {
444  if (sv[i + j] != needle[j]) {
445  match = false;
446 
447  break;
448  }
449  }
450 
451  if (match) {
452  return true;
453  }
454  }
455 
456  return false;
457 }
458 
459 } // namespace Helpers
460 
461 } // namespace vlink
Cross-platform macros for visibility, branch hints, copy prevention, singletons and string helpers.
#define VUNLIKELY(...)
Short alias for VLINK_UNLIKELY.
Definition: macros.h:289
#define VLINK_EXPORT
Definition: macros.h:81