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