diff --git a/source/blender/blenlib/BLI_string_utils.hh b/source/blender/blenlib/BLI_string_utils.hh index e536f61cff9..1e540be67fb 100644 --- a/source/blender/blenlib/BLI_string_utils.hh +++ b/source/blender/blenlib/BLI_string_utils.hh @@ -9,8 +9,11 @@ */ #include +#include #include "BLI_compiler_attrs.h" +#include "BLI_function_ref.hh" +#include "BLI_string_ref.hh" #include "BLI_utildefines.h" struct ListBase; @@ -153,6 +156,18 @@ void BLI_uniquename_cb(UniquenameCheckCallback unique_check, char delim, char *name, size_t name_maxncpy) ATTR_NONNULL(1, 3, 5); + +/** + * Ensures name is unique (according to criteria specified by caller in unique_check callback), + * incrementing its numeric suffix as necessary. + * + * \param unique_check: Return true if name is not unique + * \param delim: Delimits numeric suffix in name + * \param name: Name to be ensured unique + */ +std::string BLI_uniquename_cb(blender::FunctionRef unique_check, + char delim, + blender::StringRef name); /** * Ensures that the specified block has a unique name within the containing list, * incrementing its numeric suffix as necessary. diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index b9a0187b020..ee4e569f94d 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -550,6 +550,7 @@ if(WITH_GTESTS) tests/BLI_string_search_test.cc tests/BLI_string_test.cc tests/BLI_string_utf8_test.cc + tests/BLI_string_utils_test.cc tests/BLI_task_graph_test.cc tests/BLI_task_test.cc tests/BLI_tempfile_test.cc diff --git a/source/blender/blenlib/intern/string_utils.cc b/source/blender/blenlib/intern/string_utils.cc index f8307587504..144769ae7c9 100644 --- a/source/blender/blenlib/intern/string_utils.cc +++ b/source/blender/blenlib/intern/string_utils.cc @@ -10,8 +10,11 @@ #include #include +#include + #include "MEM_guardedalloc.h" +#include "BLI_array.hh" #include "BLI_string.h" #include "BLI_string_utf8.h" #include "BLI_string_utils.hh" @@ -424,6 +427,38 @@ void BLI_uniquename_cb(UniquenameCheckCallback unique_check, } } +std::string BLI_uniquename_cb(blender::FunctionRef unique_check, + const char delim, + const blender::StringRef name) +{ + std::string new_name = name; + + if (!unique_check(new_name)) { + return new_name; + } + + int number; + blender::Array left_buffer(new_name.size() + 1); + const size_t len = BLI_string_split_name_number( + new_name.c_str(), delim, left_buffer.data(), &number); + + const std::string left = left_buffer.data(); + + do { + std::array num_str; + BLI_snprintf(num_str.data(), num_str.size(), "%c%03d", delim, ++number); + + if (len == 0) { + new_name = num_str.data(); + } + else { + new_name = left + num_str.data(); + } + } while (unique_check(new_name)); + + return new_name; +} + /** * Generic function to set a unique name. It is only designed to be used in situations * where the name is part of the struct. diff --git a/source/blender/blenlib/tests/BLI_string_utils_test.cc b/source/blender/blenlib/tests/BLI_string_utils_test.cc new file mode 100644 index 00000000000..ab3da8baac3 --- /dev/null +++ b/source/blender/blenlib/tests/BLI_string_utils_test.cc @@ -0,0 +1,81 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: Apache-2.0 */ + +#include "BLI_string_utils.hh" + +#include + +#include "testing/testing.h" + +#include "BLI_vector.hh" + +namespace blender { + +static bool unique_check_func(void *arg, const char *name) +{ + const Vector *current_names = static_cast *>(arg); + return current_names->contains(name); +} + +TEST(BLI_string_utils, BLI_uniquename_cb) +{ + const Vector current_names{"Foo", "Bar", "Bar.003", "Baz.001", "Big.999"}; + + /* C version. */ + { + void *arg = const_cast(static_cast(¤t_names)); + + { + char name[64] = ""; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Default Name"); + } + + { + char name[64] = "Baz"; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Baz"); + } + + { + char name[64] = "Foo"; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Foo.001"); + } + + { + char name[64] = "Baz.001"; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Baz.002"); + } + + { + char name[64] = "Bar.003"; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Bar.004"); + } + + { + char name[64] = "Big.999"; + BLI_uniquename_cb(unique_check_func, arg, "Default Name", '.', name, sizeof(name)); + EXPECT_STREQ(name, "Big.1000"); + } + } + + /* C++ version. */ + { + const auto unique_check = [&](const blender::StringRef name) -> bool { + return current_names.contains(name); + }; + + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', ""), ""); + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Baz"), "Baz"); + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Foo"), "Foo.001"); + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Baz.001"), "Baz.002"); + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Bar.003"), "Bar.004"); + EXPECT_EQ(BLI_uniquename_cb(unique_check, '.', "Big.999"), "Big.1000"); + } +} + +} // namespace blender \ No newline at end of file