Porytiles
Loading...
Searching...
No Matches
string_utils.hpp
Go to the documentation of this file.
1#pragma once
2
19#include <algorithm>
20#include <cctype>
21#include <format>
22#include <ranges>
23#include <regex>
24#include <string>
25
28
29namespace porytiles {
30
43[[nodiscard]] inline bool check_full_string_match(const std::string &str, const std::string &pattern)
44{
45 try {
46 const std::regex re{pattern};
47 return std::regex_match(str, re);
48 }
49 catch (const std::regex_error &e) {
50 panic(std::string{"regex error: "} + std::string{e.what()});
51 }
52}
53
71[[nodiscard]] inline std::string trim_prefix(const std::string &str, const std::string &prefix)
72{
73 if (str.starts_with(prefix)) {
74 return str.substr(prefix.size());
75 }
76 return str;
77}
78
88inline void trim(std::string &string)
89{
90 // Trim blank space from the beginning
91 string.erase(
92 string.begin(), std::ranges::find_if(string, [](const unsigned char ch) { return !std::isspace(ch); }));
93
94 // Trim blank space from the end
95 string.erase(
96 std::ranges::find_if(string.rbegin(), string.rend(), [](const unsigned char ch) { return !std::isspace(ch); })
97 .base(),
98 string.end());
99}
100
112[[nodiscard]] inline std::vector<std::string> split(std::string input, const std::string &delimiter)
113{
114 std::vector<std::string> result;
115 size_t pos;
116 while ((pos = input.find(delimiter)) != std::string::npos) {
117 std::string token = input.substr(0, pos);
118 result.push_back(token);
119 input.erase(0, pos + delimiter.length());
120 }
121 result.push_back(input);
122 return result;
123}
124
135[[nodiscard]] inline std::string &trim_line_ending(std::string &line)
136{
137 while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
138 line.pop_back();
139 }
140 return line;
141}
142
153[[nodiscard]] inline std::string trim_line_ending(const std::string &line)
154{
155 std::string result = line;
156 while (!result.empty() && (result.back() == '\r' || result.back() == '\n')) {
157 result.pop_back();
158 }
159 return result;
160}
161
173template <typename T>
174[[nodiscard]] std::string int_to_hex_str(T t)
175{
176 return std::format("0x{:x}", t);
177}
178
190template <typename T>
191[[nodiscard]] std::string pad_two_digits(T t)
192{
193 return std::format("{:02}", t);
194}
195
207[[nodiscard]] inline std::string to_string(const std::string &str)
208{
209 return str;
210}
211
223[[nodiscard]] inline std::string to_string(bool value)
224{
225 return value ? "true" : "false";
226}
227
239template <typename T>
240[[nodiscard]] std::string to_string(const std::vector<T> &vec)
241{
242 std::string result = "{";
243 for (std::size_t i = 0; i < vec.size(); ++i) {
244 if (i > 0) {
245 result += ", ";
246 }
247 result += to_string(vec[i]);
248 }
249 result += "}";
250 return result;
251}
252
269[[nodiscard]] inline std::string to_pascal_case(const std::string &s)
270{
271 if (s.empty()) {
272 return s;
273 }
274
275 std::string result;
276 result.reserve(s.size());
277
278 bool capitalize_next = true;
279 for (const char c : s) {
280 if (c == '_' || c == '-' || c == ' ') {
281 capitalize_next = true;
282 }
283 else {
284 if (capitalize_next) {
285 result += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
286 capitalize_next = false;
287 }
288 else {
289 result += c;
290 }
291 }
292 }
293
294 return result;
295}
296
315[[nodiscard]] inline std::string to_snake_case(const std::string &s)
316{
317 if (s.empty()) {
318 return s;
319 }
320
321 std::string result;
322 result.reserve(s.size() + s.size() / 4); // Reserve extra for underscores
323
324 for (std::size_t i = 0; i < s.size(); ++i) {
325 const char c = s[i];
326
327 // Handle separators: convert to underscore
328 if (c == '_' || c == '-' || c == ' ') {
329 // Avoid leading underscore or consecutive underscores
330 if (!result.empty() && result.back() != '_') {
331 result += '_';
332 }
333 continue;
334 }
335
336 // Handle uppercase letters
337 if (std::isupper(static_cast<unsigned char>(c))) {
338 // Insert underscore before uppercase if:
339 // 1. Not at the start
340 // 2. Previous char wasn't an underscore
341 // 3. Either previous char was lowercase, OR next char is lowercase (for acronyms like XMLParser)
342 if (!result.empty() && result.back() != '_') {
343 const bool prev_is_lower = i > 0 && std::islower(static_cast<unsigned char>(s[i - 1]));
344 const bool next_is_lower = i + 1 < s.size() && std::islower(static_cast<unsigned char>(s[i + 1]));
345 if (prev_is_lower || next_is_lower) {
346 result += '_';
347 }
348 }
349 result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
350 }
351 else {
352 result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
353 }
354 }
355
356 // Remove trailing underscore if present
357 if (!result.empty() && result.back() == '_') {
358 result.pop_back();
359 }
360
361 return result;
362}
363
380[[nodiscard]] inline std::string to_lower_str(const std::string &input)
381{
382 std::string output;
383 std::ranges::transform(input, std::back_inserter(output), [](const unsigned char c) {
384 // Use unsigned char to avoid issues with negative char values
385 return std::tolower(c);
386 });
387 return output;
388}
389
400[[nodiscard]] inline std::string pal_filename(std::size_t pal_index)
401{
402 return pad_two_digits(pal_index) + ".pal";
403}
404
421[[nodiscard]] inline std::string extract_tileset_shorthand(const std::string &tileset_name)
422{
423 constexpr std::string_view prefix = "gTileset_";
424 if (tileset_name.starts_with(prefix)) {
425 return tileset_name.substr(prefix.size());
426 }
427 return tileset_name;
428}
429
446[[nodiscard]] inline DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
447{
448 return DynamicCasedName{extract_tileset_shorthand(tileset_name)};
449}
450
451} // namespace porytiles
A smart string wrapper that preserves word structure for lossless case format conversion.
std::string to_pascal_case(const std::string &s)
Converts a string to PascalCase format.
void panic(const StringViewSourceLoc &s)
Unconditionally terminates the program with a panic message.
Definition panic.cpp:43
std::string to_snake_case(const std::string &s)
Converts a string to snake_case format.
std::string & trim_line_ending(std::string &line)
Removes line ending characters from a string in-place.
bool check_full_string_match(const std::string &str, const std::string &pattern)
Checks if a string fully matches a regular expression pattern.
std::string trim_prefix(const std::string &str, const std::string &prefix)
Removes a prefix from a string if present.
@ split
Split the animation into separate animations for each palette variant (not yet implemented).
std::string int_to_hex_str(T t)
Converts an integer value to a hexadecimal string with "0x" prefix.
std::string pad_two_digits(T t)
Converts an integer value to a minimum two-digit wide string representation.
std::string to_lower_str(const std::string &input)
Converts all characters in a string to lowercase.
std::string pal_filename(std::size_t pal_index)
Constructs a palette filename from a palette index.
std::string extract_tileset_shorthand(const std::string &tileset_name)
Extracts the Pascal-case tileset short name from the full name.
DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
Extracts the tileset short name and wraps it in a DynamicCasedName.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
void trim(std::string &string)
Removes leading and trailing whitespace from a string in-place.