diff options
-rw-r--r-- | core/string/ustring.cpp | 85 | ||||
-rw-r--r-- | core/string/ustring.h | 5 | ||||
-rw-r--r-- | core/variant/variant_call.cpp | 3 | ||||
-rw-r--r-- | doc/classes/String.xml | 18 | ||||
-rw-r--r-- | editor/connections_dialog.cpp | 22 | ||||
-rw-r--r-- | editor/editor_autoload_settings.cpp | 6 | ||||
-rw-r--r-- | editor/editor_node.cpp | 8 | ||||
-rw-r--r-- | editor/plugins/bone_map_editor_plugin.cpp | 2 | ||||
-rw-r--r-- | editor/plugins/script_text_editor.cpp | 2 | ||||
-rw-r--r-- | editor/rename_dialog.cpp | 4 | ||||
-rw-r--r-- | editor/script_create_dialog.cpp | 2 | ||||
-rw-r--r-- | editor/shader_create_dialog.cpp | 2 | ||||
-rwxr-xr-x | editor/translations/extract.py | 2 | ||||
-rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs | 9 | ||||
-rw-r--r-- | modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs | 39 | ||||
-rw-r--r-- | modules/mono/glue/runtime_interop.cpp | 15 | ||||
-rw-r--r-- | scene/main/node.cpp | 11 | ||||
-rw-r--r-- | tests/core/string/test_string.h | 87 |
18 files changed, 234 insertions, 88 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index ed3d4b0b54..d8b93998af 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -970,62 +970,71 @@ const char32_t *String::get_data() const { return size() ? &operator[](0) : &zero; } -String String::capitalize() const { - String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges(); - String cap; - for (int i = 0; i < aux.get_slice_count(" "); i++) { - String slice = aux.get_slicec(' ', i); - if (slice.length() > 0) { - slice[0] = _find_upper(slice[0]); - if (i > 0) { - cap += " "; - } - cap += slice; - } - } - - return cap; -} - -String String::camelcase_to_underscore(bool lowercase) const { +String String::_camelcase_to_underscore() const { const char32_t *cstr = get_data(); String new_string; int start_index = 0; for (int i = 1; i < this->size(); i++) { - bool is_upper = is_ascii_upper_case(cstr[i]); - bool is_number = is_digit(cstr[i]); + bool is_prev_upper = is_ascii_upper_case(cstr[i - 1]); + bool is_prev_lower = is_ascii_lower_case(cstr[i - 1]); + bool is_prev_digit = is_digit(cstr[i - 1]); - bool are_next_2_lower = false; - bool is_next_lower = false; - bool is_next_number = false; - bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]); - bool was_precedent_number = is_digit(cstr[i - 1]); - - if (i + 2 < this->size()) { - are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]); - } + bool is_curr_upper = is_ascii_upper_case(cstr[i]); + bool is_curr_lower = is_ascii_lower_case(cstr[i]); + bool is_curr_digit = is_digit(cstr[i]); + bool is_next_lower = false; if (i + 1 < this->size()) { is_next_lower = is_ascii_lower_case(cstr[i + 1]); - is_next_number = is_digit(cstr[i + 1]); } - const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number; - const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower; - const bool cond_c = is_number && !was_precedent_number; - const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower; - const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number); + const bool cond_a = is_prev_lower && is_curr_upper; // aA + const bool cond_b = (is_prev_upper || is_prev_digit) && is_curr_upper && is_next_lower; // AAa, 2Aa + const bool cond_c = is_prev_digit && is_curr_lower && is_next_lower; // 2aa + const bool cond_d = (is_prev_upper || is_prev_lower) && is_curr_digit; // A2, a2 - bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number; - if (should_split) { + if (cond_a || cond_b || cond_c || cond_d) { new_string += this->substr(start_index, i - start_index) + "_"; start_index = i; } } new_string += this->substr(start_index, this->size() - start_index); - return lowercase ? new_string.to_lower() : new_string; + return new_string.to_lower(); +} + +String String::capitalize() const { + String aux = this->_camelcase_to_underscore().replace("_", " ").strip_edges(); + String cap; + for (int i = 0; i < aux.get_slice_count(" "); i++) { + String slice = aux.get_slicec(' ', i); + if (slice.length() > 0) { + slice[0] = _find_upper(slice[0]); + if (i > 0) { + cap += " "; + } + cap += slice; + } + } + + return cap; +} + +String String::to_camel_case() const { + String s = this->to_pascal_case(); + if (!s.is_empty()) { + s[0] = _find_lower(s[0]); + } + return s; +} + +String String::to_pascal_case() const { + return this->capitalize().replace(" ", ""); +} + +String String::to_snake_case() const { + return this->_camelcase_to_underscore().replace(" ", "_").strip_edges(); } String String::get_with_code_lines() const { diff --git a/core/string/ustring.h b/core/string/ustring.h index 2463fc35f7..31de7cc464 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -196,6 +196,7 @@ class String { bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; + String _camelcase_to_underscore() const; public: enum { @@ -335,7 +336,9 @@ public: static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr); String capitalize() const; - String camelcase_to_underscore(bool lowercase = true) const; + String to_camel_case() const; + String to_pascal_case() const; + String to_snake_case() const; String get_with_code_lines() const; int get_slice_count(String p_splitter) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 8e73c8dfc0..8af2a09111 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1506,6 +1506,9 @@ static void _register_variant_builtin_methods() { bind_method(String, repeat, sarray("count"), varray()); bind_method(String, insert, sarray("position", "what"), varray()); bind_method(String, capitalize, sarray(), varray()); + bind_method(String, to_camel_case, sarray(), varray()); + bind_method(String, to_pascal_case, sarray(), varray()); + bind_method(String, to_snake_case, sarray(), varray()); bind_method(String, split, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0)); bind_method(String, rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0)); bind_method(String, split_floats, sarray("delimiter", "allow_empty"), varray(true)); diff --git a/doc/classes/String.xml b/doc/classes/String.xml index c111528c06..316bb923b7 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -775,6 +775,12 @@ Converts the String (which is a character array) to ASCII/Latin-1 encoded [PackedByteArray] (which is an array of bytes). The conversion is faster compared to [method to_utf8_buffer], as this method assumes that all the characters in the String are ASCII/Latin-1 characters, unsupported characters are replaced with spaces. </description> </method> + <method name="to_camel_case" qualifiers="const"> + <return type="String" /> + <description> + Returns the string converted to [code]camelCase[/code]. + </description> + </method> <method name="to_float" qualifiers="const"> <return type="float" /> <description> @@ -804,6 +810,18 @@ Returns the string converted to lowercase. </description> </method> + <method name="to_pascal_case" qualifiers="const"> + <return type="String" /> + <description> + Returns the string converted to [code]PascalCase[/code]. + </description> + </method> + <method name="to_snake_case" qualifiers="const"> + <return type="String" /> + <description> + Returns the string converted to [code]snake_case[/code]. + </description> + </method> <method name="to_upper" qualifiers="const"> <return type="String" /> <description> diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index bacf1f1023..dce9ca2b93 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -753,22 +753,12 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) { } Dictionary subst; - - String s = node_name.capitalize().replace(" ", ""); - subst["NodeName"] = s; - if (!s.is_empty()) { - s[0] = s.to_lower()[0]; - } - subst["nodeName"] = s; - subst["node_name"] = node_name.capitalize().replace(" ", "_").to_lower(); - - s = signal_name.capitalize().replace(" ", ""); - subst["SignalName"] = s; - if (!s.is_empty()) { - s[0] = s.to_lower()[0]; - } - subst["signalName"] = s; - subst["signal_name"] = signal_name.capitalize().replace(" ", "_").to_lower(); + subst["NodeName"] = node_name.to_pascal_case(); + subst["nodeName"] = node_name.to_camel_case(); + subst["node_name"] = node_name.to_snake_case(); + subst["SignalName"] = signal_name.to_pascal_case(); + subst["signalName"] = signal_name.to_camel_case(); + subst["signal_name"] = signal_name.to_snake_case(); String dst_method = String(EDITOR_GET("interface/editors/default_signal_callback_name")).format(subst); diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 6d44654617..544b6c7141 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -164,7 +164,7 @@ void EditorAutoloadSettings::_autoload_add() { if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } - dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().camelcase_to_underscore())), false, false); + dialog->config("Node", fpath.path_join(vformat("%s.gd", autoload_add_name->get_text().to_snake_case())), false, false); dialog->popup_centered(); } else { if (autoload_add(autoload_add_name->get_text(), autoload_add_path->get_text())) { @@ -371,7 +371,7 @@ void EditorAutoloadSettings::_autoload_open(const String &fpath) { void EditorAutoloadSettings::_autoload_file_callback(const String &p_path) { // Convert the file name to PascalCase, which is the convention for classes in GDScript. - const String class_name = p_path.get_file().get_basename().capitalize().replace(" ", ""); + const String class_name = p_path.get_file().get_basename().to_pascal_case(); // If the name collides with a built-in class, prefix the name to make it possible to add without having to edit the name. // The prefix is subjective, but it provides better UX than leaving the Add button disabled :) @@ -580,7 +580,7 @@ void EditorAutoloadSettings::_script_created(Ref<Script> p_script) { FileSystemDock::get_singleton()->get_script_create_dialog()->hide(); path = p_script->get_path().get_base_dir(); autoload_add_path->set_text(p_script->get_path()); - autoload_add_name->set_text(p_script->get_path().get_file().get_basename().capitalize().replace(" ", "")); + autoload_add_name->set_text(p_script->get_path().get_file().get_basename().to_pascal_case()); _autoload_add(); } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 0b0c0a953a..e84fc77206 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1314,7 +1314,7 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String file->set_current_file(p_resource->get_path().get_file()); } else { if (extensions.size()) { - String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore(); + String resource_name_snake_case = p_resource->get_class().to_snake_case(); file->set_current_file("new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower()); } else { file->set_current_file(String()); @@ -1331,7 +1331,7 @@ void EditorNode::save_resource_as(const Ref<Resource> &p_resource, const String } else if (preferred.size()) { String existing; if (extensions.size()) { - String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore(); + String resource_name_snake_case = p_resource->get_class().to_snake_case(); existing = "new_" + resource_name_snake_case + "." + preferred.front()->get().to_lower(); } file->set_current_path(existing); @@ -2730,10 +2730,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { // Use casing of the root node. break; case SCENE_NAME_CASING_PASCAL_CASE: { - root_name = root_name.capitalize().replace(" ", ""); + root_name = root_name.to_pascal_case(); } break; case SCENE_NAME_CASING_SNAKE_CASE: - root_name = root_name.capitalize().replace(" ", "").replace("-", "_").camelcase_to_underscore(); + root_name = root_name.replace("-", "_").to_snake_case(); break; } file->set_current_path(root_name + "." + extensions.front()->get().to_lower()); diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp index c16dca00a3..988f9cc394 100644 --- a/editor/plugins/bone_map_editor_plugin.cpp +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -609,7 +609,7 @@ int BoneMapper::search_bone_by_name(Skeleton3D *p_skeleton, Vector<String> p_pic } BoneMapper::BoneSegregation BoneMapper::guess_bone_segregation(String p_bone_name) { - String fixed_bn = p_bone_name.camelcase_to_underscore().to_lower(); + String fixed_bn = p_bone_name.to_snake_case(); LocalVector<String> left_words; left_words.push_back("(?<![a-zA-Z])left"); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 85e53011d6..8bb9452938 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1590,7 +1590,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } } - String variable_name = String(node->get_name()).camelcase_to_underscore(true).validate_identifier(); + String variable_name = String(node->get_name()).to_snake_case().validate_identifier(); if (use_type) { text_to_drop += vformat("@onready var %s: %s = %s%s\n", variable_name, node->get_class_name(), is_unique ? "%" : "$", path); } else { diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp index 7b4df696b7..300a3d0f05 100644 --- a/editor/rename_dialog.cpp +++ b/editor/rename_dialog.cpp @@ -500,7 +500,7 @@ String RenameDialog::_postprocess(const String &subject) { if (style_id == 1) { // PascalCase to snake_case - result = result.camelcase_to_underscore(true); + result = result.to_snake_case(); result = _regex("_+", result, "_"); } else if (style_id == 2) { @@ -521,7 +521,7 @@ String RenameDialog::_postprocess(const String &subject) { end = start + 1; } buffer += result.substr(end, result.size() - (end + 1)); - result = buffer.replace("_", "").capitalize(); + result = buffer.to_pascal_case(); } } diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index e09862263c..7d065b4920 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -898,7 +898,7 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL // Get name from file name if no name in meta information if (script_template.name == String()) { - script_template.name = p_filename.get_basename().replace("_", " ").capitalize(); + script_template.name = p_filename.get_basename().capitalize(); } return script_template; diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index 8c4a231e8a..522fd7c645 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -161,7 +161,7 @@ void ShaderCreateDialog::_create_new() { shader = text_shader; StringBuilder code; - code += vformat("shader_type %s;\n", mode_menu->get_text().replace(" ", "").camelcase_to_underscore()); + code += vformat("shader_type %s;\n", mode_menu->get_text().to_snake_case()); if (current_template == 0) { // Default template. code += "\n"; diff --git a/editor/translations/extract.py b/editor/translations/extract.py index 7f3da400e7..07026baee2 100755 --- a/editor/translations/extract.py +++ b/editor/translations/extract.py @@ -139,7 +139,7 @@ theme_property_patterns = { } -# See String::camelcase_to_underscore(). +# See String::_camelcase_to_underscore(). capitalize_re = re.compile(r"(?<=\D)(?=\d)|(?<=\d)(?=\D([a-z]|\d))") diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 94dda5a74b..e84bba1179 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -436,6 +436,15 @@ namespace Godot.NativeInterop public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); + public static partial void godotsharp_string_to_camel_case(in godot_string p_self, + out godot_string r_camel_case); + + public static partial void godotsharp_string_to_pascal_case(in godot_string p_self, + out godot_string r_pascal_case); + + public static partial void godotsharp_string_to_snake_case(in godot_string p_self, + out godot_string r_snake_case); + // NodePath public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index f0bc5949df..44f951e314 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -287,6 +287,45 @@ namespace Godot return cap; } + /// <summary> + /// Returns the string converted to <c>camelCase</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToCamelCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_camel_case(instanceStr, out godot_string camelCase); + using (camelCase) + return Marshaling.ConvertStringToManaged(camelCase); + } + + /// <summary> + /// Returns the string converted to <c>PascalCase</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToPascalCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_pascal_case(instanceStr, out godot_string pascalCase); + using (pascalCase) + return Marshaling.ConvertStringToManaged(pascalCase); + } + + /// <summary> + /// Returns the string converted to <c>snake_case</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToSnakeCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_snake_case(instanceStr, out godot_string snakeCase); + using (snakeCase) + return Marshaling.ConvertStringToManaged(snakeCase); + } + private static string CamelcaseToUnderscore(this string instance, bool lowerCase) { string newString = string.Empty; diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 0d68cb54b9..529bda4c38 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1096,6 +1096,18 @@ void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_ memnew_placement(r_simplified_path, String(p_self->simplify_path())); } +void godotsharp_string_to_camel_case(const String *p_self, String *r_camel_case) { + memnew_placement(r_camel_case, String(p_self->to_camel_case())); +} + +void godotsharp_string_to_pascal_case(const String *p_self, String *r_pascal_case) { + memnew_placement(r_pascal_case, String(p_self->to_pascal_case())); +} + +void godotsharp_string_to_snake_case(const String *p_self, String *r_snake_case) { + memnew_placement(r_snake_case, String(p_self->to_snake_case())); +} + void godotsharp_node_path_get_as_property_path(const NodePath *p_ptr, NodePath *r_dest) { memnew_placement(r_dest, NodePath(p_ptr->get_as_property_path())); } @@ -1471,6 +1483,9 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_string_sha256_buffer, (void *)godotsharp_string_sha256_text, (void *)godotsharp_string_simplify_path, + (void *)godotsharp_string_to_camel_case, + (void *)godotsharp_string_to_pascal_case, + (void *)godotsharp_string_to_snake_case, (void *)godotsharp_node_path_get_as_property_path, (void *)godotsharp_node_path_get_concatenated_names, (void *)godotsharp_node_path_get_concatenated_subnames, diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 4b28e4076f..289e963077 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -951,14 +951,11 @@ String Node::validate_child_name(Node *p_child) { String Node::adjust_name_casing(const String &p_name) { switch (GLOBAL_GET("editor/node_naming/name_casing").operator int()) { case NAME_CASING_PASCAL_CASE: - return p_name.capitalize().replace(" ", ""); - case NAME_CASING_CAMEL_CASE: { - String name = p_name.capitalize().replace(" ", ""); - name[0] = name.to_lower()[0]; - return name; - } + return p_name.to_pascal_case(); + case NAME_CASING_CAMEL_CASE: + return p_name.to_camel_case(); case NAME_CASING_SNAKE_CASE: - return p_name.capitalize().replace(" ", "_").to_lower(); + return p_name.to_snake_case(); } return p_name; } diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index 2cd837bcc9..d97da05c04 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -466,11 +466,6 @@ TEST_CASE("[String] String to float") { } } -TEST_CASE("[String] CamelCase to underscore") { - CHECK(String("TestTestStringGD").camelcase_to_underscore(false) == String("Test_Test_String_GD")); - CHECK(String("TestTestStringGD").camelcase_to_underscore(true) == String("test_test_string_gd")); -} - TEST_CASE("[String] Slicing") { String s = "Mars,Jupiter,Saturn,Uranus"; @@ -1096,8 +1091,36 @@ TEST_CASE("[String] IPVX address to string") { } TEST_CASE("[String] Capitalize against many strings") { - String input = "bytes2var"; - String output = "Bytes 2 Var"; + String input = "2D"; + String output = "2d"; + CHECK(input.capitalize() == output); + + input = "2d"; + output = "2d"; + CHECK(input.capitalize() == output); + + input = "2db"; + output = "2 Db"; + CHECK(input.capitalize() == output); + + input = "HTML5 Html5 html5 html_5"; + output = "Html 5 Html 5 Html 5 Html 5"; + CHECK(input.capitalize() == output); + + input = "Node2D Node2d NODE2D NODE_2D node_2d"; + output = "Node 2d Node 2d Node 2d Node 2d Node 2d"; + CHECK(input.capitalize() == output); + + input = "Node2DPosition"; + output = "Node 2d Position"; + CHECK(input.capitalize() == output); + + input = "Number2Digits"; + output = "Number 2 Digits"; + CHECK(input.capitalize() == output); + + input = "bytes2var"; + output = "Bytes 2 Var"; CHECK(input.capitalize() == output); input = "linear2db"; @@ -1112,10 +1135,6 @@ TEST_CASE("[String] Capitalize against many strings") { output = "Sha 256"; CHECK(input.capitalize() == output); - input = "2db"; - output = "2 Db"; - CHECK(input.capitalize() == output); - input = "PascalCase"; output = "Pascal Case"; CHECK(input.capitalize() == output); @@ -1153,6 +1172,50 @@ TEST_CASE("[String] Capitalize against many strings") { CHECK(input.capitalize() == output); } +struct StringCasesTestCase { + const char *input; + const char *camel_case; + const char *pascal_case; + const char *snake_case; +}; + +TEST_CASE("[String] Checking case conversion methods") { + StringCasesTestCase test_cases[] = { + /* clang-format off */ + { "2D", "2d", "2d", "2d" }, + { "2d", "2d", "2d", "2d" }, + { "2db", "2Db", "2Db", "2_db" }, + { "Vector3", "vector3", "Vector3", "vector_3" }, + { "sha256", "sha256", "Sha256", "sha_256" }, + { "Node2D", "node2d", "Node2d", "node_2d" }, + { "RichTextLabel", "richTextLabel", "RichTextLabel", "rich_text_label" }, + { "HTML5", "html5", "Html5", "html_5" }, + { "Node2DPosition", "node2dPosition", "Node2dPosition", "node_2d_position" }, + { "Number2Digits", "number2Digits", "Number2Digits", "number_2_digits" }, + { "get_property_list", "getPropertyList", "GetPropertyList", "get_property_list" }, + { "get_camera_2d", "getCamera2d", "GetCamera2d", "get_camera_2d" }, + { "_physics_process", "physicsProcess", "PhysicsProcess", "_physics_process" }, + { "bytes2var", "bytes2Var", "Bytes2Var", "bytes_2_var" }, + { "linear2db", "linear2Db", "Linear2Db", "linear_2_db" }, + { "sha256sum", "sha256Sum", "Sha256Sum", "sha_256_sum" }, + { "camelCase", "camelCase", "CamelCase", "camel_case" }, + { "PascalCase", "pascalCase", "PascalCase", "pascal_case" }, + { "snake_case", "snakeCase", "SnakeCase", "snake_case" }, + { "Test TEST test", "testTestTest", "TestTestTest", "test_test_test" }, + { nullptr, nullptr, nullptr, nullptr }, + /* clang-format on */ + }; + + int idx = 0; + while (test_cases[idx].input != nullptr) { + String input = test_cases[idx].input; + CHECK(input.to_camel_case() == test_cases[idx].camel_case); + CHECK(input.to_pascal_case() == test_cases[idx].pascal_case); + CHECK(input.to_snake_case() == test_cases[idx].snake_case); + idx++; + } +} + TEST_CASE("[String] Checking string is empty when it should be") { bool state = true; bool success; @@ -1663,7 +1726,7 @@ TEST_CASE("[String] Variant ptr indexed set") { TEST_CASE("[Stress][String] Empty via ' == String()'") { for (int i = 0; i < 100000; ++i) { String str = "Hello World!"; - if (str.is_empty()) { + if (str == String()) { continue; } } |