diff --git a/CHANGELOG.md b/CHANGELOG.md index b5183d0a..b6d49f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ - Replaced archived `github.com/AlecAivazis/survey` dependency with a simpler `osutils.YesNoPrompt(message, fallback)` helper. +- Added `inflector.Camelize(str)` and `inflector.Singularize(str)` helper methods. + - Other minor improvements (_replaced all `bool` exists db scans with `int` for broader drivers compatibility, use the non-transactional app instance during realtime records delete access checks to ensure that cascade deleted records with API rules relying on the parent will be resolved, updated UI dependencies, etc._) diff --git a/tools/inflector/inflector.go b/tools/inflector/inflector.go index 3fa1ab13..5a9f307e 100644 --- a/tools/inflector/inflector.go +++ b/tools/inflector/inflector.go @@ -83,3 +83,30 @@ func Snakecase(str string) string { return strings.ToLower(result.String()) } + +// Camelize convers a string to its "CamelCased" version (non alphanumeric characters are removed). +// +// For example: +// +// inflector.Camelize("send_email") // "SendEmail" +func Camelize(str string) string { + var result strings.Builder + + var isPrevSpecial bool + + for _, c := range str { + if !unicode.IsLetter(c) && !unicode.IsNumber(c) { + isPrevSpecial = true + continue + } + + if isPrevSpecial || result.Len() == 0 { + isPrevSpecial = false + result.WriteRune(unicode.ToUpper(c)) + } else { + result.WriteRune(c) + } + } + + return result.String() +} diff --git a/tools/inflector/inflector_test.go b/tools/inflector/inflector_test.go index fef9f1df..0b96dd28 100644 --- a/tools/inflector/inflector_test.go +++ b/tools/inflector/inflector_test.go @@ -143,3 +143,33 @@ func TestSnakecase(t *testing.T) { }) } } + +func TestCamelize(t *testing.T) { + scenarios := []struct { + val string + expected string + }{ + {"", ""}, + {" ", ""}, + {"Test", "Test"}, + {"test", "Test"}, + {"testTest2", "TestTest2"}, + {"TestTest2", "TestTest2"}, + {"test test2", "TestTest2"}, + {"test-test2", "TestTest2"}, + {"test'test2", "TestTest2"}, + {"test1test2", "Test1test2"}, + {"1test-test2", "1testTest2"}, + {"123", "123"}, + {"123a", "123a"}, + } + + for i, s := range scenarios { + t.Run(fmt.Sprintf("%d_%#v", i, s.val), func(t *testing.T) { + result := inflector.Camelize(s.val) + if result != s.expected { + t.Fatalf("Expected %q, got %q", s.expected, result) + } + }) + } +} diff --git a/tools/inflector/singularize.go b/tools/inflector/singularize.go index 294ac287..07aab24c 100644 --- a/tools/inflector/singularize.go +++ b/tools/inflector/singularize.go @@ -56,7 +56,7 @@ var singularRules = []struct { {"(?i)s$", ""}, } -// Singularize returns the singular version of the specified word. +// Singularize converts the specified word into its singular version. // // For example: //