-
-
Notifications
You must be signed in to change notification settings - Fork 14
Implement a to_title_case text utility function
#2394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME text | ||
| SOURCES text.cc) | ||
|
|
||
| if(SOURCEMETA_CORE_INSTALL) | ||
| sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME text) | ||
| endif() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| #ifndef SOURCEMETA_CORE_TEXT_H_ | ||
| #define SOURCEMETA_CORE_TEXT_H_ | ||
|
|
||
| #ifndef SOURCEMETA_CORE_TEXT_EXPORT | ||
| #include <sourcemeta/core/text_export.h> | ||
| #endif | ||
|
|
||
| #include <string> // std::string | ||
|
|
||
| /// @defgroup text Text | ||
| /// @brief A collection of general-purpose text manipulation utilities | ||
| /// | ||
| /// This functionality is included as follows: | ||
| /// | ||
| /// ```cpp | ||
| /// #include <sourcemeta/core/text.h> | ||
| /// ``` | ||
|
|
||
| namespace sourcemeta::core { | ||
|
|
||
| /// @ingroup text | ||
| /// | ||
| /// Convert a string to Title Case in place. For example: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc comment calls this “Title Case”, but the implementation only uppercases the first character of each segment and leaves the rest unchanged (it does not lowercase existing uppercase letters). Consider clarifying the documented semantics so callers don’t assume full title-casing behavior. Severity: low 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||
| /// | ||
| /// ```cpp | ||
| /// #include <sourcemeta/core/text.h> | ||
| /// #include <cassert> | ||
| /// #include <string> | ||
| /// | ||
| /// std::string value{"hello_world"}; | ||
| /// sourcemeta::core::to_title_case(value); | ||
| /// assert(value == "Hello World"); | ||
| /// ``` | ||
| SOURCEMETA_CORE_TEXT_EXPORT | ||
| auto to_title_case(std::string &value) -> void; | ||
|
|
||
| } // namespace sourcemeta::core | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| #include <sourcemeta/core/text.h> | ||
|
|
||
| #include <cctype> // std::isalpha, std::toupper | ||
| #include <cstddef> // std::size_t | ||
|
|
||
| namespace sourcemeta::core { | ||
|
|
||
| auto to_title_case(std::string &value) -> void { | ||
| std::size_t write{0}; | ||
| bool capitalize_next{true}; | ||
| bool pending_separator{false}; | ||
| for (const char character : value) { | ||
| if (character == '_' || character == '-') { | ||
| if (write > 0) { | ||
| pending_separator = true; | ||
| } | ||
| } else { | ||
| if (pending_separator) { | ||
| value[write++] = ' '; | ||
| pending_separator = false; | ||
| capitalize_next = true; | ||
| } | ||
| if (capitalize_next) { | ||
| value[write++] = static_cast<char>( | ||
| std::toupper(static_cast<unsigned char>(character))); | ||
| if (std::isalpha(static_cast<unsigned char>(character))) { | ||
| capitalize_next = false; | ||
| } | ||
| } else { | ||
| value[write++] = character; | ||
| } | ||
| } | ||
| } | ||
| value.resize(write); | ||
| } | ||
|
|
||
| } // namespace sourcemeta::core |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME text | ||
| SOURCES text_to_title_case_test.cc) | ||
|
|
||
| target_link_libraries(sourcemeta_core_text_unit | ||
| PRIVATE sourcemeta::core::text) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| #include <gtest/gtest.h> | ||
|
|
||
| #include <sourcemeta/core/text.h> | ||
|
|
||
| #include <string> // std::string | ||
|
|
||
| TEST(Text_to_title_case, empty_string) { | ||
| std::string value{""}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, ""); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_lowercase_character) { | ||
| std::string value{"a"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "A"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_uppercase_character) { | ||
| std::string value{"A"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "A"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_underscore_is_empty) { | ||
| std::string value{"_"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, ""); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_dash_is_empty) { | ||
| std::string value{"-"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, ""); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, only_separators_is_empty) { | ||
| std::string value{"___"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, ""); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_lowercase_word) { | ||
| std::string value{"hello"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, already_title_cased_word) { | ||
| std::string value{"Hello"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, all_uppercase_word) { | ||
| std::string value{"HELLO"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "HELLO"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, snake_case_two_words) { | ||
| std::string value{"hello_world"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, kebab_case_two_words) { | ||
| std::string value{"hello-world"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, mixed_snake_and_kebab_separators) { | ||
| std::string value{"hello_world-test"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World Test"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, snake_case_three_words) { | ||
| std::string value{"abc_def_ghi"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Abc Def Ghi"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, preserves_existing_uppercase_after_separator) { | ||
| std::string value{"Hello_World"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, leading_underscore_is_stripped) { | ||
| std::string value{"_hello"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, leading_dash_is_stripped) { | ||
| std::string value{"-hello"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, trailing_underscore_is_stripped) { | ||
| std::string value{"hello_"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, trailing_dash_is_stripped) { | ||
| std::string value{"hello-"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, separators_around_word_are_stripped) { | ||
| std::string value{"_hello_"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, multiple_leading_separators_are_stripped) { | ||
| std::string value{"__hello"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, multiple_trailing_separators_are_stripped) { | ||
| std::string value{"hello__"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, consecutive_separators_collapse_to_single_space) { | ||
| std::string value{"hello__world"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, mixed_consecutive_separators_collapse) { | ||
| std::string value{"hello_-world"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello World"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, single_letter_words) { | ||
| std::string value{"a_b_c"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "A B C"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, digits_pass_through) { | ||
| std::string value{"abc123def"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Abc123def"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, digit_after_separator) { | ||
| std::string value{"abc_123"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Abc 123"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, letter_after_leading_digits_in_segment) { | ||
| std::string value{"abc_123def"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Abc 123Def"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, leading_digits_then_letter_at_start) { | ||
| std::string value{"123abc"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "123Abc"); | ||
| } | ||
|
|
||
| TEST(Text_to_title_case, space_in_input_is_not_a_separator) { | ||
| std::string value{"hello world"}; | ||
| sourcemeta::core::to_title_case(value); | ||
| EXPECT_EQ(value, "Hello world"); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding
textto the defaultSOURCEMETA_CORE_COMPONENTSlist means an installation built with-DSOURCEMETA_CORE_LANG_TEXT=OFFcould still try toinclude(.../sourcemeta_core_text.cmake)by default and fail atfind_packagetime if that export wasn’t installed. Consider guarding default component selection / inclusion based on whether the component was built and installed.Severity: medium
Other Locations
config.cmake.in:141🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.