From 68495d1a9e085a58d18891319695fc989260f0b7 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 17 Jun 2024 11:30:29 +0800 Subject: [PATCH 1/7] feat: add string conversion functions --- string.go | 59 ++++++++++++++++++++ string_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) diff --git a/string.go b/string.go index a7a959a3..4de9a0e4 100644 --- a/string.go +++ b/string.go @@ -2,7 +2,9 @@ package lo import ( "math/rand" + "regexp" "strings" + "unicode" "unicode/utf8" ) @@ -94,3 +96,60 @@ func ChunkString[T ~string](str T, size int) []T { func RuneLength(str string) int { return utf8.RuneCountInString(str) } + +// PascalCase converts string to pascal case. +func PascalCase(str string) string { + items := Words(str) + for i, item := range items { + items[i] = Capitalize(strings.ToLower(item)) + } + return strings.Join(items, "") +} + +// CamelCase converts string to camel case. +func CamelCase(str string) string { + items := Words(str) + for i, item := range items { + item = strings.ToLower(item) + if i > 0 { + item = Capitalize(item) + } + items[i] = item + } + return strings.Join(items, "") +} + +// KebabCase converts string to kebab case. +func KebabCase(str string) string { + return strings.Join(Words(str), "-") +} + +// SnakeCase converts string to snake case. +func SnakeCase(str string) string { + return strings.Join(Words(str), "_") +} + +// Words splits string into an array of its words. +func Words(str string) []string { + reg := regexp.MustCompile(`([a-z])([A-Z])|([a-zA-Z])([0-9])|([0-9])([a-zA-Z])`) + str = reg.ReplaceAllString(str, `$1$3$5 $2$4$6`) + var result strings.Builder + for _, r := range str { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + result.WriteRune(r) + } else { + result.WriteRune(' ') + } + } + return strings.Fields(result.String()) +} + +// Capitalize converts the first character of string to upper case and the remaining to lower case. +func Capitalize(word string) string { + if len(word) == 0 { + return word + } + runes := []rune(word) + runes[0] = unicode.ToUpper(runes[0]) + return string(runes) +} diff --git a/string_test.go b/string_test.go index 42832586..9915018c 100644 --- a/string_test.go +++ b/string_test.go @@ -100,3 +100,147 @@ func TestRuneLength(t *testing.T) { is.Equal(5, RuneLength("hellô")) is.Equal(6, len("hellô")) } + +func TestPascalCase(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"", "hello_world", "HelloWorld"}, + {"", "helloWorld", "HelloWorld"}, + {"", "__hello_world-example string--", "HelloWorldExampleString"}, + {"", "WITH UPPERCASE LETTERS", "WithUppercaseLetters"}, + {"", "test123_string", "Test123String"}, + {"", "test123string", "Test123String"}, + {"", "", ""}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual := PascalCase(test.input) + if actual != test.expected { + t.Errorf("PascalCase(%q) = %q; expected %q", test.input, actual, test.expected) + } + }) + } +} + +func TestCamelCase(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"", "hello_world", "helloWorld"}, + {"", "helloWorld", "helloWorld"}, + {"", "__hello_world-example string--", "helloWorldExampleString"}, + {"", "WITH UPPERCASE LETTERS", "withUppercaseLetters"}, + {"", "test123_string", "test123String"}, + {"", "test123string", "test123String"}, + {"", "", ""}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := CamelCase(test.input) + if result != test.expected { + t.Errorf("CamelCase(%q) = %q; want %q", test.input, result, test.expected) + } + }) + } +} + +func TestKebabCase(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"", "hello world", "hello-world"}, + {"", "HelloWorld", "hello-world"}, + {"", "KebabCase", "kebab-case"}, + {"", "already-kebab-case", "already-kebab-case"}, + {"", "Already-Kebab-Case", "already-kebab-case"}, + {"", "multiple spaces", "multiple-spaces"}, + {"", "", ""}, + {"", "Single", "single"}, + {"", "123_abs", "123-abs"}, + {"", "SINGLE", "single"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := KebabCase(test.input) + if result != test.expected { + t.Errorf("KebabCase(%q) = %q; want %q", test.input, result, test.expected) + } + }) + } +} + +func TestSnakeCase(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + {"", "CamelCase", "camel_case"}, + {"", "snakeCase", "snake_case"}, + {"", "snake-case", "snake_case"}, + {"", "SnakeCaseTest", "snake_case_test"}, + {"", "Snake_Case_With_Underscores", "snake_case_with_underscores"}, + {"", "lowercase", "lowercase"}, + {"", "UPPERCASE", "uppercase"}, + {"", "", ""}, + } + + for _, test := range tests { + t.Run(test.input, func(t *testing.T) { + got := SnakeCase(test.input) + if got != test.expected { + t.Errorf("SnakeCase(%q) = %q; want %q", test.input, got, test.expected) + } + }) + } +} + +func TestWords(t *testing.T) { + type args struct { + str string + } + tests := []struct { + name string + args args + want []string + }{ + {"", args{"CamelCase"}, []string{"Camel", "Case"}}, + {"", args{"snakeCase"}, []string{"snake", "Case"}}, + {"", args{"snake-case"}, []string{"snake", "case"}}, + {"", args{"test123string"}, []string{"test", "123", "string"}}, + {"", args{"UPPERCASE"}, []string{"UPPERCASE"}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, Words(tt.args.str), "words(%v)", tt.args.str) + }) + } +} + +func TestCapitalize(t *testing.T) { + type args struct { + word string + } + tests := []struct { + name string + args args + want string + }{ + {"", args{"hello"}, "Hello"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equalf(t, tt.want, Capitalize(tt.args.word), "Capitalize(%v)", tt.args.word) + }) + } +} From a786b13f1379f33b05c903188e5919da2265f2e3 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 17 Jun 2024 12:10:39 +0800 Subject: [PATCH 2/7] fix: fix `Capitalize`, update tests --- string.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/string.go b/string.go index 4de9a0e4..5c178570 100644 --- a/string.go +++ b/string.go @@ -121,12 +121,16 @@ func CamelCase(str string) string { // KebabCase converts string to kebab case. func KebabCase(str string) string { - return strings.Join(Words(str), "-") + return strings.Join(Map(Words(str), func(item string, index int) string { + return strings.ToLower(item) + }), "-") } // SnakeCase converts string to snake case. func SnakeCase(str string) string { - return strings.Join(Words(str), "_") + return strings.Join(Map(Words(str), func(item string, index int) string { + return strings.ToLower(item) + }), "_") } // Words splits string into an array of its words. @@ -145,11 +149,17 @@ func Words(str string) []string { } // Capitalize converts the first character of string to upper case and the remaining to lower case. -func Capitalize(word string) string { - if len(word) == 0 { - return word +func Capitalize(str string) string { + if len(str) == 0 { + return str + } + runes := []rune(str) + for i, r := range runes { + if i == 0 { + runes[i] = unicode.ToUpper(r) + } else { + runes[i] = unicode.ToLower(r) + } } - runes := []rune(word) - runes[0] = unicode.ToUpper(runes[0]) return string(runes) } From 9656a1b0ba4d34c312b6a15a66c3139b6a612492 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 17 Jun 2024 12:10:49 +0800 Subject: [PATCH 3/7] fix: fix `Capitalize`, update tests --- string_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/string_test.go b/string_test.go index 9915018c..f1ce286f 100644 --- a/string_test.go +++ b/string_test.go @@ -214,9 +214,11 @@ func TestWords(t *testing.T) { args args want []string }{ - {"", args{"CamelCase"}, []string{"Camel", "Case"}}, - {"", args{"snakeCase"}, []string{"snake", "Case"}}, - {"", args{"snake-case"}, []string{"snake", "case"}}, + {"", args{"PascalCase"}, []string{"Pascal", "Case"}}, + {"", args{"camelCase"}, []string{"camel", "Case"}}, + {"", args{"snake_case"}, []string{"snake", "case"}}, + {"", args{"kebab_case"}, []string{"kebab", "case"}}, + {"", args{"_test text_"}, []string{"test", "text"}}, {"", args{"test123string"}, []string{"test", "123", "string"}}, {"", args{"UPPERCASE"}, []string{"UPPERCASE"}}, } @@ -237,6 +239,7 @@ func TestCapitalize(t *testing.T) { want string }{ {"", args{"hello"}, "Hello"}, + {"", args{"heLLO"}, "Hello"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From dbb1fe2ba7ee028d7ee6adaa8310043bdc036837 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Mon, 17 Jun 2024 12:21:48 +0800 Subject: [PATCH 4/7] update README.md --- README.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/README.md b/README.md index dd848c39..6dd487ff 100644 --- a/README.md +++ b/README.md @@ -1297,6 +1297,72 @@ sub := len("hellô") [[play](https://go.dev/play/p/tuhgW_lWY8l)] +### PascalCase + +Converts string to pascal case. + +```go +str := lo.PascalCase("hello_world") +// HelloWorld +``` + +[[play](https://go.dev/play/p/iZkdeLP9oiB)] + +### CamelCase + +Converts string to camel case. + +```go +str := lo.CamelCase("hello_world") +// helloWorld +``` + +[[play](https://go.dev/play/p/dtyFB58MBRp)] + +### KebabCase + +Converts string to kebab case. + +```go +str := lo.KebabCase("helloWorld") +// hello-world +``` + +[[play](https://go.dev/play/p/2YTuPafwECA)] + +### SnakeCase + +Converts string to snake case. + +```go +str := lo.SnakeCase("HelloWorld") +// hello_world +``` + +[[play](https://go.dev/play/p/QVKJG9nOnDg)] + +### Words + +Splits string into an array of its words. + +```go +str := lo.Words("helloWorld") +// []string{"hello", "world"} +``` + +[[play](https://go.dev/play/p/2P4zhqqq61g)] + +### Capitalize + +Converts the first character of string to upper case and the remaining to lower case. + +```go +str := lo.PascalCase("heLLO") +// Hello +``` + +[[play](https://go.dev/play/p/jBIJ_OFtFYp)] + ### T2 -> T9 Creates a tuple from a list of values. From 754471a376bc409209702a635f863965df6ce39a Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Tue, 18 Jun 2024 14:38:27 +0800 Subject: [PATCH 5/7] update tests --- string.go | 8 +- string_test.go | 418 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 333 insertions(+), 93 deletions(-) diff --git a/string.go b/string.go index 5c178570..29ecc488 100644 --- a/string.go +++ b/string.go @@ -16,6 +16,9 @@ var ( AlphanumericCharset = append(LettersCharset, NumbersCharset...) SpecialCharset = []rune("!@#$%^&*()_+-=[]{}|;':\",./<>?") AllCharset = append(AlphanumericCharset, SpecialCharset...) + + splitWordReg = regexp.MustCompile(`([a-z])([A-Z0-9])|([a-zA-Z])([0-9])|([0-9])([a-zA-Z])|([A-Z])([A-Z])([a-z])`) + splitNumberLetterReg = regexp.MustCompile(`([0-9])([a-zA-Z])`) ) // RandomString return a random string. @@ -135,8 +138,9 @@ func SnakeCase(str string) string { // Words splits string into an array of its words. func Words(str string) []string { - reg := regexp.MustCompile(`([a-z])([A-Z])|([a-zA-Z])([0-9])|([0-9])([a-zA-Z])`) - str = reg.ReplaceAllString(str, `$1$3$5 $2$4$6`) + str = splitWordReg.ReplaceAllString(str, `$1$3$5$7 $2$4$6$8$9`) + // example: Int8Value => Int 8Value => Int 8 Value + str = splitNumberLetterReg.ReplaceAllString(str, "$1 $2") var result strings.Builder for _, r := range str { if unicode.IsLetter(r) || unicode.IsDigit(r) { diff --git a/string_test.go b/string_test.go index f1ce286f..97034320 100644 --- a/string_test.go +++ b/string_test.go @@ -101,105 +101,340 @@ func TestRuneLength(t *testing.T) { is.Equal(6, len("hellô")) } -func TestPascalCase(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"", "hello_world", "HelloWorld"}, - {"", "helloWorld", "HelloWorld"}, - {"", "__hello_world-example string--", "HelloWorldExampleString"}, - {"", "WITH UPPERCASE LETTERS", "WithUppercaseLetters"}, - {"", "test123_string", "Test123String"}, - {"", "test123string", "Test123String"}, - {"", "", ""}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - actual := PascalCase(test.input) - if actual != test.expected { - t.Errorf("PascalCase(%q) = %q; expected %q", test.input, actual, test.expected) - } - }) +func TestAllCase(t *testing.T) { + type output struct { + PascalCase string + CamelCase string + KebabCase string + SnakeCase string } -} - -func TestCamelCase(t *testing.T) { + name := "" tests := []struct { - name string - input string - expected string + name string + input string + output output }{ - {"", "hello_world", "helloWorld"}, - {"", "helloWorld", "helloWorld"}, - {"", "__hello_world-example string--", "helloWorldExampleString"}, - {"", "WITH UPPERCASE LETTERS", "withUppercaseLetters"}, - {"", "test123_string", "test123String"}, - {"", "test123string", "test123String"}, - {"", "", ""}, + {name: name, output: output{}}, + {name: name, input: ".", output: output{}}, + {name: name, input: "Hello world!", output: output{ + PascalCase: "HelloWorld", + CamelCase: "helloWorld", + KebabCase: "hello-world", + SnakeCase: "hello_world", + }}, + {name: name, input: "A", output: output{ + PascalCase: "A", + CamelCase: "a", + KebabCase: "a", + SnakeCase: "a", + }}, + {name: name, input: "a", output: output{ + PascalCase: "A", + CamelCase: "a", + KebabCase: "a", + SnakeCase: "a", + }}, + {name: name, input: "foo", output: output{ + PascalCase: "Foo", + CamelCase: "foo", + KebabCase: "foo", + SnakeCase: "foo", + }}, + {name: name, input: "snake_case", output: output{ + PascalCase: "SnakeCase", + CamelCase: "snakeCase", + KebabCase: "snake-case", + SnakeCase: "snake_case", + }}, + {name: name, input: "SNAKE_CASE", output: output{ + PascalCase: "SnakeCase", + CamelCase: "snakeCase", + KebabCase: "snake-case", + SnakeCase: "snake_case", + }}, + {name: name, input: "kebab-case", output: output{ + PascalCase: "KebabCase", + CamelCase: "kebabCase", + KebabCase: "kebab-case", + SnakeCase: "kebab_case", + }}, + {name: name, input: "PascalCase", output: output{ + PascalCase: "PascalCase", + CamelCase: "pascalCase", + KebabCase: "pascal-case", + SnakeCase: "pascal_case", + }}, + {name: name, input: "camelCase", output: output{ + PascalCase: "CamelCase", + CamelCase: "camelCase", + KebabCase: `camel-case`, + SnakeCase: "camel_case", + }}, + {name: name, input: "Title Case", output: output{ + PascalCase: "TitleCase", + CamelCase: "titleCase", + KebabCase: "title-case", + SnakeCase: "title_case", + }}, + {name: name, input: "point.case", output: output{ + PascalCase: "PointCase", + CamelCase: "pointCase", + KebabCase: "point-case", + SnakeCase: "point_case", + }}, + {name: name, input: "snake_case_with_more_words", output: output{ + PascalCase: "SnakeCaseWithMoreWords", + CamelCase: "snakeCaseWithMoreWords", + KebabCase: "snake-case-with-more-words", + SnakeCase: "snake_case_with_more_words", + }}, + {name: name, input: "SNAKE_CASE_WITH_MORE_WORDS", output: output{ + PascalCase: "SnakeCaseWithMoreWords", + CamelCase: "snakeCaseWithMoreWords", + KebabCase: "snake-case-with-more-words", + SnakeCase: "snake_case_with_more_words", + }}, + {name: name, input: "kebab-case-with-more-words", output: output{ + PascalCase: "KebabCaseWithMoreWords", + CamelCase: "kebabCaseWithMoreWords", + KebabCase: "kebab-case-with-more-words", + SnakeCase: "kebab_case_with_more_words", + }}, + {name: name, input: "PascalCaseWithMoreWords", output: output{ + PascalCase: "PascalCaseWithMoreWords", + CamelCase: "pascalCaseWithMoreWords", + KebabCase: "pascal-case-with-more-words", + SnakeCase: "pascal_case_with_more_words", + }}, + {name: name, input: "camelCaseWithMoreWords", output: output{ + PascalCase: "CamelCaseWithMoreWords", + CamelCase: "camelCaseWithMoreWords", + KebabCase: "camel-case-with-more-words", + SnakeCase: "camel_case_with_more_words", + }}, + {name: name, input: "Title Case With More Words", output: output{ + PascalCase: "TitleCaseWithMoreWords", + CamelCase: "titleCaseWithMoreWords", + KebabCase: "title-case-with-more-words", + SnakeCase: "title_case_with_more_words", + }}, + {name: name, input: "point.case.with.more.words", output: output{ + PascalCase: "PointCaseWithMoreWords", + CamelCase: "pointCaseWithMoreWords", + KebabCase: "point-case-with-more-words", + SnakeCase: "point_case_with_more_words", + }}, + {name: name, input: "snake_case__with___multiple____delimiters", output: output{ + PascalCase: "SnakeCaseWithMultipleDelimiters", + CamelCase: "snakeCaseWithMultipleDelimiters", + KebabCase: "snake-case-with-multiple-delimiters", + SnakeCase: "snake_case_with_multiple_delimiters", + }}, + {name: name, input: "SNAKE_CASE__WITH___multiple____DELIMITERS", output: output{ + PascalCase: "SnakeCaseWithMultipleDelimiters", + CamelCase: "snakeCaseWithMultipleDelimiters", + KebabCase: "snake-case-with-multiple-delimiters", + SnakeCase: "snake_case_with_multiple_delimiters", + }}, + {name: name, input: "kebab-case--with---multiple----delimiters", output: output{ + PascalCase: "KebabCaseWithMultipleDelimiters", + CamelCase: "kebabCaseWithMultipleDelimiters", + KebabCase: "kebab-case-with-multiple-delimiters", + SnakeCase: "kebab_case_with_multiple_delimiters", + }}, + {name: name, input: "Title Case With Multiple Delimiters", output: output{ + PascalCase: "TitleCaseWithMultipleDelimiters", + CamelCase: "titleCaseWithMultipleDelimiters", + KebabCase: "title-case-with-multiple-delimiters", + SnakeCase: "title_case_with_multiple_delimiters", + }}, + {name: name, input: "point.case..with...multiple....delimiters", output: output{ + PascalCase: "PointCaseWithMultipleDelimiters", + CamelCase: "pointCaseWithMultipleDelimiters", + KebabCase: "point-case-with-multiple-delimiters", + SnakeCase: "point_case_with_multiple_delimiters", + }}, + {name: name, input: " leading space", output: output{ + PascalCase: "LeadingSpace", + CamelCase: "leadingSpace", + KebabCase: "leading-space", + SnakeCase: "leading_space", + }}, + {name: name, input: " leading spaces", output: output{ + PascalCase: "LeadingSpaces", + CamelCase: "leadingSpaces", + KebabCase: "leading-spaces", + SnakeCase: "leading_spaces", + }}, + {name: name, input: "\t\t\r\n leading whitespaces", output: output{ + PascalCase: "LeadingWhitespaces", + CamelCase: "leadingWhitespaces", + KebabCase: "leading-whitespaces", + SnakeCase: "leading_whitespaces", + }}, + {name: name, input: "trailing space ", output: output{ + PascalCase: "TrailingSpace", + CamelCase: "trailingSpace", + KebabCase: "trailing-space", + SnakeCase: "trailing_space", + }}, + {name: name, input: "trailing spaces ", output: output{ + PascalCase: "TrailingSpaces", + CamelCase: "trailingSpaces", + KebabCase: "trailing-spaces", + SnakeCase: "trailing_spaces", + }}, + {name: name, input: "trailing whitespaces\t\t\r\n", output: output{ + PascalCase: "TrailingWhitespaces", + CamelCase: "trailingWhitespaces", + KebabCase: "trailing-whitespaces", + SnakeCase: "trailing_whitespaces", + }}, + {name: name, input: " on both sides ", output: output{ + PascalCase: "OnBothSides", + CamelCase: "onBothSides", + KebabCase: "on-both-sides", + SnakeCase: "on_both_sides", + }}, + {name: name, input: " many on both sides ", output: output{ + PascalCase: "ManyOnBothSides", + CamelCase: "manyOnBothSides", + KebabCase: "many-on-both-sides", + SnakeCase: "many_on_both_sides", + }}, + {name: name, input: "\r whitespaces on both sides\t\t\r\n", output: output{ + PascalCase: "WhitespacesOnBothSides", + CamelCase: "whitespacesOnBothSides", + KebabCase: "whitespaces-on-both-sides", + SnakeCase: "whitespaces_on_both_sides", + }}, + {name: name, input: " extraSpaces in_This TestCase Of MIXED_CASES\t", output: output{ + PascalCase: "ExtraSpacesInThisTestCaseOfMixedCases", + CamelCase: "extraSpacesInThisTestCaseOfMixedCases", + KebabCase: "extra-spaces-in-this-test-case-of-mixed-cases", + SnakeCase: "extra_spaces_in_this_test_case_of_mixed_cases", + }}, + {name: name, input: "CASEBreak", output: output{ + PascalCase: "CaseBreak", + CamelCase: "caseBreak", + KebabCase: "case-break", + SnakeCase: "case_break", + }}, + {name: name, input: "ID", output: output{ + PascalCase: "Id", + CamelCase: "id", + KebabCase: "id", + SnakeCase: "id", + }}, + {name: name, input: "userID", output: output{ + PascalCase: "UserId", + CamelCase: "userId", + KebabCase: "user-id", + SnakeCase: "user_id", + }}, + {name: name, input: "JSON_blob", output: output{ + PascalCase: "JsonBlob", + CamelCase: "jsonBlob", + KebabCase: "json-blob", + SnakeCase: "json_blob", + }}, + {name: name, input: "HTTPStatusCode", output: output{ + PascalCase: "HttpStatusCode", + CamelCase: "httpStatusCode", + KebabCase: "http-status-code", + SnakeCase: "http_status_code", + }}, + {name: name, input: "FreeBSD and SSLError are not golang initialisms", output: output{ + PascalCase: "FreeBsdAndSslErrorAreNotGolangInitialisms", + CamelCase: "freeBsdAndSslErrorAreNotGolangInitialisms", + KebabCase: "free-bsd-and-ssl-error-are-not-golang-initialisms", + SnakeCase: "free_bsd_and_ssl_error_are_not_golang_initialisms", + }}, + {name: name, input: "David's Computer", output: output{ + PascalCase: "DavidSComputer", + CamelCase: "davidSComputer", + KebabCase: "david-s-computer", + SnakeCase: "david_s_computer", + }}, + {name: name, input: "http200", output: output{ + PascalCase: "Http200", + CamelCase: "http200", + KebabCase: "http-200", + SnakeCase: "http_200", + }}, + {name: name, input: "NumberSplittingVersion1.0r3", output: output{ + PascalCase: "NumberSplittingVersion10R3", + CamelCase: "numberSplittingVersion10R3", + KebabCase: "number-splitting-version-1-0-r3", + SnakeCase: "number_splitting_version_1_0_r3", + }}, + {name: name, input: "When you have a comma, odd results", output: output{ + PascalCase: "WhenYouHaveACommaOddResults", + CamelCase: "whenYouHaveACommaOddResults", + KebabCase: "when-you-have-a-comma-odd-results", + SnakeCase: "when_you_have_a_comma_odd_results", + }}, + {name: name, input: "Ordinal numbers work: 1st 2nd and 3rd place", output: output{ + PascalCase: "OrdinalNumbersWork1St2NdAnd3RdPlace", + CamelCase: "ordinalNumbersWork1St2NdAnd3RdPlace", + KebabCase: "ordinal-numbers-work-1-st-2-nd-and-3-rd-place", + SnakeCase: "ordinal_numbers_work_1_st_2_nd_and_3_rd_place", + }}, + {name: name, input: "BadUTF8\xe2\xe2\xa1", output: output{ + PascalCase: "BadUtf8", + CamelCase: "badUtf8", + KebabCase: "bad-utf-8", + SnakeCase: "bad_utf_8", + }}, + {name: name, input: "IDENT3", output: output{ + PascalCase: "Ident3", + CamelCase: "ident3", + KebabCase: "ident-3", + SnakeCase: "ident_3", + }}, + {name: name, input: "LogRouterS3BucketName", output: output{ + PascalCase: "LogRouterS3BucketName", + CamelCase: "logRouterS3BucketName", + KebabCase: "log-router-s3-bucket-name", + SnakeCase: "log_router_s3_bucket_name", + }}, + {name: name, input: "PINEAPPLE", output: output{ + PascalCase: "Pineapple", + CamelCase: "pineapple", + KebabCase: "pineapple", + SnakeCase: "pineapple", + }}, + {name: name, input: "Int8Value", output: output{ + PascalCase: "Int8Value", + CamelCase: "int8Value", + KebabCase: "int-8-value", + SnakeCase: "int_8_value", + }}, + {name: name, input: "first.last", output: output{ + PascalCase: "FirstLast", + CamelCase: "firstLast", + KebabCase: "first-last", + SnakeCase: "first_last", + }}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result := CamelCase(test.input) - if result != test.expected { - t.Errorf("CamelCase(%q) = %q; want %q", test.input, result, test.expected) + pascal := PascalCase(test.input) + if pascal != test.output.PascalCase { + t.Errorf("PascalCase(%q) = %q; expected %q", test.input, pascal, test.output.PascalCase) } - }) - } -} - -func TestKebabCase(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"", "hello world", "hello-world"}, - {"", "HelloWorld", "hello-world"}, - {"", "KebabCase", "kebab-case"}, - {"", "already-kebab-case", "already-kebab-case"}, - {"", "Already-Kebab-Case", "already-kebab-case"}, - {"", "multiple spaces", "multiple-spaces"}, - {"", "", ""}, - {"", "Single", "single"}, - {"", "123_abs", "123-abs"}, - {"", "SINGLE", "single"}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - result := KebabCase(test.input) - if result != test.expected { - t.Errorf("KebabCase(%q) = %q; want %q", test.input, result, test.expected) + camel := CamelCase(test.input) + if camel != test.output.CamelCase { + t.Errorf("CamelCase(%q) = %q; expected %q", test.input, camel, test.output.CamelCase) } - }) - } -} - -func TestSnakeCase(t *testing.T) { - tests := []struct { - name string - input string - expected string - }{ - {"", "CamelCase", "camel_case"}, - {"", "snakeCase", "snake_case"}, - {"", "snake-case", "snake_case"}, - {"", "SnakeCaseTest", "snake_case_test"}, - {"", "Snake_Case_With_Underscores", "snake_case_with_underscores"}, - {"", "lowercase", "lowercase"}, - {"", "UPPERCASE", "uppercase"}, - {"", "", ""}, - } - - for _, test := range tests { - t.Run(test.input, func(t *testing.T) { - got := SnakeCase(test.input) - if got != test.expected { - t.Errorf("SnakeCase(%q) = %q; want %q", test.input, got, test.expected) + kebab := KebabCase(test.input) + if kebab != test.output.KebabCase { + t.Errorf("KebabCase(%q) = %q; expected %q", test.input, kebab, test.output.KebabCase) + } + snake := SnakeCase(test.input) + if snake != test.output.SnakeCase { + t.Errorf("SnakeCase(%q) = %q; expected %q", test.input, snake, test.output.SnakeCase) } }) } @@ -219,8 +454,9 @@ func TestWords(t *testing.T) { {"", args{"snake_case"}, []string{"snake", "case"}}, {"", args{"kebab_case"}, []string{"kebab", "case"}}, {"", args{"_test text_"}, []string{"test", "text"}}, - {"", args{"test123string"}, []string{"test", "123", "string"}}, {"", args{"UPPERCASE"}, []string{"UPPERCASE"}}, + {"", args{"HTTPCode"}, []string{"HTTP", "Code"}}, + {"", args{"Int8Value"}, []string{"Int", "8", "Value"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 6d178cd9caf7214f0b2b388b5143918eb325ea26 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Tue, 18 Jun 2024 15:58:56 +0800 Subject: [PATCH 6/7] update `Capitalize` --- go.mod | 1 + go.sum | 2 ++ string.go | 17 ++++------------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index ff196ab3..3dd65690 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/thoas/go-funk v0.9.1 go.uber.org/goleak v1.2.1 golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 + golang.org/x/text v0.16.0 ) require ( diff --git a/go.sum b/go.sum index ffe90473..7490ad11 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/string.go b/string.go index 29ecc488..fa961546 100644 --- a/string.go +++ b/string.go @@ -1,6 +1,8 @@ package lo import ( + "golang.org/x/text/cases" + "golang.org/x/text/language" "math/rand" "regexp" "strings" @@ -104,7 +106,7 @@ func RuneLength(str string) int { func PascalCase(str string) string { items := Words(str) for i, item := range items { - items[i] = Capitalize(strings.ToLower(item)) + items[i] = Capitalize(item) } return strings.Join(items, "") } @@ -154,16 +156,5 @@ func Words(str string) []string { // Capitalize converts the first character of string to upper case and the remaining to lower case. func Capitalize(str string) string { - if len(str) == 0 { - return str - } - runes := []rune(str) - for i, r := range runes { - if i == 0 { - runes[i] = unicode.ToUpper(r) - } else { - runes[i] = unicode.ToLower(r) - } - } - return string(runes) + return cases.Title(language.English).String(str) } From 5f102d0a997ed16bd9b497ed74e3d1c72dc83f66 Mon Sep 17 00:00:00 2001 From: eiixy <990656271@qq.com> Date: Tue, 18 Jun 2024 16:27:03 +0800 Subject: [PATCH 7/7] style: unify coding style --- string.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/string.go b/string.go index fa961546..ea49ee80 100644 --- a/string.go +++ b/string.go @@ -126,16 +126,20 @@ func CamelCase(str string) string { // KebabCase converts string to kebab case. func KebabCase(str string) string { - return strings.Join(Map(Words(str), func(item string, index int) string { - return strings.ToLower(item) - }), "-") + items := Words(str) + for i, item := range items { + items[i] = strings.ToLower(item) + } + return strings.Join(items, "-") } // SnakeCase converts string to snake case. func SnakeCase(str string) string { - return strings.Join(Map(Words(str), func(item string, index int) string { - return strings.ToLower(item) - }), "_") + items := Words(str) + for i, item := range items { + items[i] = strings.ToLower(item) + } + return strings.Join(items, "_") } // Words splits string into an array of its words.