Porytiles
Loading...
Searching...
No Matches
dynamic_cased_name.cpp
Go to the documentation of this file.
2
3#include <cctype>
4#include <string>
5#include <vector>
6
7namespace porytiles {
8namespace {
9
21[[nodiscard]] std::vector<std::string> split_pascal_words(const std::string &token)
22{
23 if (token.empty()) {
24 return {};
25 }
26
27 std::vector<std::string> words;
28 std::string current_word;
29
30 for (std::size_t i = 0; i < token.size(); ++i) {
31 const char c = token[i];
32
33 if (std::isupper(static_cast<unsigned char>(c))) {
34 /*
35 * Insert a word break before this uppercase char if:
36 * 1. We have accumulated characters already, AND
37 * 2. Either the previous char was lowercase, OR the next char is lowercase (acronym boundary)
38 */
39 if (!current_word.empty()) {
40 const bool prev_is_lower = i > 0 && std::islower(static_cast<unsigned char>(token[i - 1]));
41 const bool next_is_lower =
42 i + 1 < token.size() && std::islower(static_cast<unsigned char>(token[i + 1]));
43 if (prev_is_lower || next_is_lower) {
44 words.push_back(current_word);
45 current_word.clear();
46 }
47 }
48 current_word += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
49 }
50 else {
51 current_word += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
52 }
53 }
54
55 if (!current_word.empty()) {
56 words.push_back(current_word);
57 }
58
59 return words;
60}
61
68[[nodiscard]] std::vector<std::string> split_on_underscores(const std::string &input)
69{
70 std::vector<std::string> tokens;
71 std::string current;
72
73 for (const char c : input) {
74 if (c == '_') {
75 if (!current.empty()) {
76 tokens.push_back(current);
77 current.clear();
78 }
79 }
80 else {
81 current += c;
82 }
83 }
84
85 if (!current.empty()) {
86 tokens.push_back(current);
87 }
88
89 return tokens;
90}
91
98[[nodiscard]] std::string capitalize(const std::string &word)
99{
100 if (word.empty()) {
101 return word;
102 }
103 std::string result = word;
104 result[0] = static_cast<char>(std::toupper(static_cast<unsigned char>(result[0])));
105 return result;
106}
107
108} // namespace
109
110DynamicCasedName::DynamicCasedName(std::vector<std::vector<std::string>> segments) : segments_{std::move(segments)}
111{
112 compute_canonical();
113}
114
115void DynamicCasedName::compute_canonical()
116{
117 canonical_.clear();
118 for (const auto &segment : segments_) {
119 for (const auto &word : segment) {
120 canonical_ += word;
121 }
122 }
123}
124
125DynamicCasedName::DynamicCasedName(const std::string &input)
126{
127 if (input.empty()) {
128 return;
129 }
130
131 bool has_underscore = false;
132 bool has_uppercase = false;
133
134 for (const char c : input) {
135 if (c == '_') {
136 has_underscore = true;
137 }
138 if (std::isupper(static_cast<unsigned char>(c))) {
139 has_uppercase = true;
140 }
141 }
142
143 if (has_underscore && has_uppercase) {
144 *this = from_c_identifier(input);
145 }
146 else if (has_underscore) {
147 *this = from_snake_case(input);
148 }
149 else if (has_uppercase) {
150 *this = from_pascal_case(input);
151 }
152 else {
153 *this = from_flat_case(input);
154 }
155}
156
157DynamicCasedName DynamicCasedName::from_snake_case(const std::string &input)
158{
159 if (input.empty()) {
160 return DynamicCasedName{};
161 }
162
163 std::vector<std::string> tokens = split_on_underscores(input);
164 std::vector<std::vector<std::string>> segments;
165 segments.reserve(tokens.size());
166
167 for (auto &token : tokens) {
168 // Lowercase the token
169 std::string lower;
170 lower.reserve(token.size());
171 for (const char c : token) {
172 lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
173 }
174 segments.push_back({std::move(lower)});
175 }
176
177 return DynamicCasedName{std::move(segments)};
178}
179
180DynamicCasedName DynamicCasedName::from_pascal_case(const std::string &input)
181{
182 if (input.empty()) {
183 return DynamicCasedName{};
184 }
185
186 std::vector<std::string> words = split_pascal_words(input);
187 if (words.empty()) {
188 return DynamicCasedName{};
189 }
190
191 return DynamicCasedName{std::vector<std::vector<std::string>>{std::move(words)}};
192}
193
194DynamicCasedName DynamicCasedName::from_c_identifier(const std::string &input)
195{
196 if (input.empty()) {
197 return DynamicCasedName{};
198 }
199
200 std::vector<std::string> tokens = split_on_underscores(input);
201 std::vector<std::vector<std::string>> segments;
202 segments.reserve(tokens.size());
203
204 for (const auto &token : tokens) {
205 std::vector<std::string> words = split_pascal_words(token);
206 if (!words.empty()) {
207 segments.push_back(std::move(words));
208 }
209 }
210
211 return DynamicCasedName{std::move(segments)};
212}
213
214DynamicCasedName DynamicCasedName::from_flat_case(const std::string &input)
215{
216 if (input.empty()) {
217 return DynamicCasedName{};
218 }
219
220 std::string lower;
221 lower.reserve(input.size());
222 for (const char c : input) {
223 lower += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
224 }
225
226 return DynamicCasedName{std::vector<std::vector<std::string>>{{std::move(lower)}}};
227}
228
229std::string DynamicCasedName::to_snake_case() const
230{
231 std::string result;
232 bool first = true;
233
234 for (const auto &segment : segments_) {
235 for (const auto &word : segment) {
236 if (!first) {
237 result += '_';
238 }
239 result += word;
240 first = false;
241 }
242 }
243
244 return result;
245}
246
247std::string DynamicCasedName::to_pascal_case() const
248{
249 std::string result;
250
251 for (const auto &segment : segments_) {
252 for (const auto &word : segment) {
253 result += capitalize(word);
254 }
255 }
256
257 return result;
258}
259
260std::string DynamicCasedName::to_c_identifier() const
261{
262 std::string result;
263 bool first_segment = true;
264
265 for (const auto &segment : segments_) {
266 if (!first_segment) {
267 result += '_';
268 }
269 for (const auto &word : segment) {
270 result += capitalize(word);
271 }
272 first_segment = false;
273 }
274
275 return result;
276}
277
278std::string DynamicCasedName::to_flat_case() const
279{
280 return canonical_;
281}
282
283std::string to_string(const DynamicCasedName &value)
284{
285 return value.to_snake_case();
286}
287
288} // namespace porytiles
A smart string wrapper that preserves word structure for lossless case format conversion.
std::string to_snake_case() const
Outputs all words flattened and joined with underscores.
std::string to_string(const PrimaryPairingMode m)
Converts a PrimaryPairingMode to its canonical string representation.