VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
Strings.cpp
Go to the documentation of this file.
2#include <algorithm>
3#include <cctype>
4#include <charconv>
5#include <cstring>
6#include <limits>
7#include <sstream>
8#include <string_view>
9
10namespace VoltMod
11{
12
13std::optional<int64_t> ParseInt64(std::string_view text)
14{
15 int64_t value{};
16 auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), value);
17 if (ec != std::errc{} || ptr != text.data() + text.size())
18 {
19 return std::nullopt;
20 }
21 return value;
22}
23
24std::optional<uint64_t> ParseUInt64(std::string_view text)
25{
26 uint64_t value{};
27 auto [ptr, ec] = std::from_chars(text.data(), text.data() + text.size(), value);
28 if (ec != std::errc{} || ptr != text.data() + text.size())
29 {
30 return std::nullopt;
31 }
32 return value;
33}
34
35int ParseDuration(std::string_view text)
36{
37 while (!text.empty() && std::isspace(static_cast<unsigned char>(text.front())))
38 {
39 text.remove_prefix(1);
40 }
41 while (!text.empty() && std::isspace(static_cast<unsigned char>(text.back())))
42 {
43 text.remove_suffix(1);
44 }
45
46 if (text.empty())
47 {
48 return -1;
49 }
50
51 std::string lower(text);
52 std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
53 if (lower == "0" || lower == "perm" || lower == "permanent")
54 {
55 return 0;
56 }
57
59 char suffix = lower.back();
60 if (!std::isdigit(static_cast<unsigned char>(suffix)))
61 {
62 switch (suffix)
63 {
64 case 's':
65 multiplier = 1;
66 break;
67 case 'm':
68 multiplier = 60;
69 break;
70 case 'h':
71 multiplier = 3600;
72 break;
73 case 'd':
74 multiplier = 86400;
75 break;
76 case 'w':
77 multiplier = 604800;
78 break;
79 default:
80 return -1;
81 }
82 lower.pop_back();
83 }
84
85 int value = 0;
86 auto [ptr, ec] = std::from_chars(lower.data(), lower.data() + lower.size(), value);
87 if (ec != std::errc{} || ptr != lower.data() + lower.size() || value < 0)
88 {
89 return -1;
90 }
91
92 int64_t total = static_cast<int64_t>(value) * multiplier;
93 if (total > std::numeric_limits<int>::max())
94 {
95 return -1;
96 }
97 return static_cast<int>(total);
98}
99
100std::string Strings::ToLower(std::string_view str)
101{
102 std::string result(str);
103 std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return std::tolower(c); });
104 return result;
105}
106
107std::string Strings::Trim(std::string_view str)
108{
109 auto begin = std::find_if(str.begin(), str.end(), [](unsigned char c) { return !std::isspace(c); });
110 auto end = std::find_if(str.rbegin(), str.rend(), [](unsigned char c) { return !std::isspace(c); }).base();
111 return begin < end ? std::string(begin, end) : std::string();
112}
113
114std::string Strings::Join(const std::vector<std::string>& parts, std::string_view delimiter)
115{
116 if (parts.empty())
117 {
118 return "";
119 }
120
121 std::string result = parts[0];
122 for (size_t i = 1; i < parts.size(); ++i)
123 {
124 result += delimiter;
125 result += parts[i];
126 }
127 return result;
128}
129
130std::string Strings::JoinNonEmpty(const std::vector<std::string>& parts, std::string_view delimiter)
131{
132 std::string result;
133 for (const auto& part : parts)
134 {
135 if (part.empty())
136 {
137 continue;
138 }
139 if (!result.empty())
140 {
141 result += delimiter;
142 }
143 result += part;
144 }
145 return result;
146}
147
148bool Strings::StartsWith(std::string_view str, std::string_view prefix)
149{
150 return str.starts_with(prefix);
151}
152
153bool Strings::ContainsIgnoreCase(std::string_view str, std::string_view substr)
154{
155 return ToLower(str).find(ToLower(substr)) != std::string::npos;
156}
157
158// SubstituteTokens is the only caller; not public API.
159static std::string ReplaceAll(const std::string& str, const std::string& from, const std::string& to)
160{
161 if (from.empty())
162 {
163 return str;
164 }
165
166 std::string result = str;
167 size_t pos = 0;
168 while ((pos = result.find(from, pos)) != std::string::npos)
169 {
170 result.replace(pos, from.length(), to);
171 pos += to.length();
172 }
173 return result;
174}
175
176std::string Strings::SubstituteTokens(std::string text, const Tokens& tokens)
177{
178 for (const auto& [key, value] : tokens)
179 {
180 text = ReplaceAll(text, "{" + key + "}", value);
181 }
182 return text;
183}
184
185std::string Strings::EscapeHtml(std::string_view text)
186{
187 std::string out;
188 out.reserve(text.size());
189 for (char c : text)
190 {
191 switch (c)
192 {
193 case '&':
194 out += "&amp;";
195 break;
196 case '<':
197 out += "&lt;";
198 break;
199 case '>':
200 out += "&gt;";
201 break;
202 case '"':
203 out += "&quot;";
204 break;
205 case '\'':
206 out += "&#39;";
207 break;
208 default:
209 out += c;
210 break;
211 }
212 }
213 return out;
214}
215
216std::string Strings::QuoteConsoleArg(std::string_view value)
217{
218 std::string out;
219 out.reserve(value.size() + 2);
220 out += '"';
221 for (char c : value)
222 {
223 if (c == '"' || c == '\\')
224 {
225 out += '\\';
226 }
227 out += c;
228 }
229 out += '"';
230 return out;
231}
232
233std::string Strings::TruncateUtf8(std::string_view text, std::size_t maxBytes, std::string_view ellipsis)
234{
235 if (text.size() <= maxBytes)
236 {
237 return std::string(text);
238 }
239 std::size_t end = maxBytes;
240 // Back up past UTF-8 continuation bytes so the cut never splits a multibyte sequence.
241 while (end > 0 && (static_cast<unsigned char>(text[end]) & 0xC0) == 0x80)
242 {
243 --end;
244 }
245 return std::string(text.substr(0, end)).append(ellipsis);
246}
247
248bool Strings::IsNumeric(std::string_view str)
249{
250 if (str.empty())
251 {
252 return false;
253 }
254 return std::all_of(str.begin(), str.end(), [](unsigned char c) { return std::isdigit(c); });
255}
256
257void Strings::CopyToBuffer(char* buffer, std::size_t size, std::string_view text) noexcept
258{
259 if (buffer == nullptr || size == 0)
260 {
261 return;
262 }
263
264 const std::size_t length = std::min(size - 1, text.size());
265 std::memcpy(buffer, text.data(), length);
266 buffer[length] = '\0';
267}
268
269std::string Strings::DisplayNameOr(int64_t id, std::string_view name, std::size_t maxBytes)
270{
271 // A name is player-controlled and this is row markup, so it is escaped here rather than at
272 // each call site: an unescaped '<' would let a player close the row's font tag.
273 return name.empty() ? std::to_string(id) : EscapeHtml(TruncateUtf8(name, maxBytes));
274}
275
276} // namespace VoltMod
static std::string Trim(std::string_view str)
Definition Strings.cpp:107
static void CopyToBuffer(char *buffer, std::size_t size, std::string_view text) noexcept
Definition Strings.cpp:257
static std::string DisplayNameOr(int64_t id, std::string_view name, std::size_t maxBytes=20)
Definition Strings.cpp:269
static bool ContainsIgnoreCase(std::string_view str, std::string_view substr)
Definition Strings.cpp:153
static std::string QuoteConsoleArg(std::string_view value)
Definition Strings.cpp:216
static std::string JoinNonEmpty(const std::vector< std::string > &parts, std::string_view delimiter)
Definition Strings.cpp:130
static bool StartsWith(std::string_view str, std::string_view prefix)
Definition Strings.cpp:148
static std::string SubstituteTokens(std::string text, const Tokens &tokens)
Definition Strings.cpp:176
static std::string TruncateUtf8(std::string_view text, std::size_t maxBytes, std::string_view ellipsis="...")
Definition Strings.cpp:233
static std::string Join(const std::vector< std::string > &parts, std::string_view delimiter)
Definition Strings.cpp:114
static std::string ToLower(std::string_view str)
Definition Strings.cpp:100
static bool IsNumeric(std::string_view str)
Definition Strings.cpp:248
static std::string EscapeHtml(std::string_view text)
Definition Strings.cpp:185
std::optional< int64_t > ParseInt64(std::string_view text)
Definition Strings.cpp:13
int ParseDuration(std::string_view text)
Definition Strings.cpp:35
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
std::optional< uint64_t > ParseUInt64(std::string_view text)
Definition Strings.cpp:24
std::map< std::string, std::string > Tokens
Definition Strings.hpp:14
static std::string ReplaceAll(const std::string &str, const std::string &from, const std::string &to)
Definition Strings.cpp:159