Porytiles
Loading...
Searching...
No Matches
string_utils.hpp
Go to the documentation of this file.
1#pragma once
2
16
17#include <algorithm>
18#include <cctype>
19#include <format>
20#include <ranges>
21#include <regex>
22#include <string>
23#include <vector>
24
27
28namespace porytiles {
29
40[[nodiscard]] inline bool check_full_string_match(const std::string &str, const std::string &pattern)
41{
42 try {
43 const std::regex re{pattern};
44 return std::regex_match(str, re);
45 }
46 catch (const std::regex_error &e) {
47 panic(std::string{"regex error: "} + std::string{e.what()});
48 }
49}
50
66[[nodiscard]] inline std::string trim_prefix(const std::string &str, const std::string &prefix)
67{
68 if (str.starts_with(prefix)) {
69 return str.substr(prefix.size());
70 }
71 return str;
72}
73
81inline void trim(std::string &string)
82{
83 // Trim blank space from the beginning
84 string.erase(
85 string.begin(), std::ranges::find_if(string, [](const unsigned char ch) { return !std::isspace(ch); }));
86
87 // Trim blank space from the end
88 string.erase(
89 std::ranges::find_if(string.rbegin(), string.rend(), [](const unsigned char ch) { return !std::isspace(ch); })
90 .base(),
91 string.end());
92}
93
103[[nodiscard]] inline std::vector<std::string> split(std::string input, const std::string &delimiter)
104{
105 std::vector<std::string> result;
106 size_t pos;
107 while ((pos = input.find(delimiter)) != std::string::npos) {
108 std::string token = input.substr(0, pos);
109 result.push_back(token);
110 input.erase(0, pos + delimiter.length());
111 }
112 result.push_back(input);
113 return result;
114}
115
131[[nodiscard]] inline std::string
132join_quoted(const std::vector<std::string> &values, const std::string &delimiter = ", ")
133{
134 std::string joined;
135 for (const auto &value : values) {
136 if (!joined.empty()) {
137 joined += delimiter;
138 }
139 joined += "'" + value + "'";
140 }
141 return joined;
142}
143
152[[nodiscard]] inline std::string &trim_line_ending(std::string &line)
153{
154 while (!line.empty() && (line.back() == '\r' || line.back() == '\n')) {
155 line.pop_back();
156 }
157 return line;
158}
159
168[[nodiscard]] inline std::string trim_line_ending(const std::string &line)
169{
170 std::string result = line;
171 while (!result.empty() && (result.back() == '\r' || result.back() == '\n')) {
172 result.pop_back();
173 }
174 return result;
175}
176
186template <typename T>
187[[nodiscard]] std::string int_to_hex_str(T t)
188{
189 return std::format("0x{:x}", t);
190}
191
201template <typename T>
202[[nodiscard]] std::string pad_two_digits(T t)
203{
204 return std::format("{:02}", t);
205}
206
216[[nodiscard]] inline std::string to_string(const std::string &str)
217{
218 return str;
219}
220
230[[nodiscard]] inline std::string to_string(bool value)
231{
232 return value ? "true" : "false";
233}
234
244template <typename T>
245[[nodiscard]] std::string to_string(const std::vector<T> &vec)
246{
247 std::string result = "{";
248 for (std::size_t i = 0; i < vec.size(); ++i) {
249 if (i > 0) {
250 result += ", ";
251 }
252 result += to_string(vec[i]);
253 }
254 result += "}";
255 return result;
256}
257
272[[nodiscard]] inline std::string to_pascal_case(const std::string &s)
273{
274 if (s.empty()) {
275 return s;
276 }
277
278 std::string result;
279 result.reserve(s.size());
280
281 bool capitalize_next = true;
282 for (const char c : s) {
283 if (c == '_' || c == '-' || c == ' ') {
284 capitalize_next = true;
285 }
286 else {
287 if (capitalize_next) {
288 result += static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
289 capitalize_next = false;
290 }
291 else {
292 result += c;
293 }
294 }
295 }
296
297 return result;
298}
299
316[[nodiscard]] inline std::string to_snake_case(const std::string &s)
317{
318 if (s.empty()) {
319 return s;
320 }
321
322 std::string result;
323 result.reserve(s.size() + s.size() / 4); // Reserve extra for underscores
324
325 for (std::size_t i = 0; i < s.size(); ++i) {
326 const char c = s[i];
327
328 // Handle separators: convert to underscore
329 if (c == '_' || c == '-' || c == ' ') {
330 // Avoid leading underscore or consecutive underscores
331 if (!result.empty() && result.back() != '_') {
332 result += '_';
333 }
334 continue;
335 }
336
337 // Handle uppercase letters
338 if (std::isupper(static_cast<unsigned char>(c))) {
339 // Insert underscore before uppercase if:
340 // 1. Not at the start
341 // 2. Previous char wasn't an underscore
342 // 3. Either previous char was lowercase, OR next char is lowercase (for acronyms like XMLParser)
343 if (!result.empty() && result.back() != '_') {
344 const bool prev_is_lower = i > 0 && std::islower(static_cast<unsigned char>(s[i - 1]));
345 const bool next_is_lower = i + 1 < s.size() && std::islower(static_cast<unsigned char>(s[i + 1]));
346 if (prev_is_lower || next_is_lower) {
347 result += '_';
348 }
349 }
350 result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
351 }
352 else {
353 result += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
354 }
355 }
356
357 // Remove trailing underscore if present
358 if (!result.empty() && result.back() == '_') {
359 result.pop_back();
360 }
361
362 return result;
363}
364
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
398[[nodiscard]] inline std::string palette_filename(std::size_t palette_index)
399{
400 return pad_two_digits(palette_index) + ".pal";
401}
402
417[[nodiscard]] inline std::string extract_tileset_shorthand(const std::string &tileset_name)
418{
419 constexpr std::string_view prefix = "gTileset_";
420 if (tileset_name.starts_with(prefix)) {
421 return tileset_name.substr(prefix.size());
422 }
423 return tileset_name;
424}
425
440[[nodiscard]] inline DynamicCasedName extract_tileset_cased_name(const std::string &tileset_name)
441{
442 return DynamicCasedName{extract_tileset_shorthand(tileset_name)};
443}
444
445} // 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.
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 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 palette_filename(std::size_t palette_index)
Constructs a palette filename from a palette index.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.
@ split
Split the animation into separate animations for each palette variant (not yet implemented).
void trim(std::string &string)
Removes leading and trailing whitespace from a string in-place.
std::string join_quoted(const std::vector< std::string > &values, const std::string &delimiter=", ")
Joins strings into a delimited list, wrapping each element in single quotes.