From ea1277d76cafff27404db6dd83749c6774807133 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 30 Aug 2019 20:51:23 +0800 Subject: [PATCH] add case functions for gstr and according unit test cases --- text/gstr/gstr_case.go | 142 ++++++++++++++++++++++++ text/gstr/gstr_z_unit_case_test.go | 171 +++++++++++++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 text/gstr/gstr_case.go create mode 100644 text/gstr/gstr_z_unit_case_test.go diff --git a/text/gstr/gstr_case.go b/text/gstr/gstr_case.go new file mode 100644 index 000000000..722aa7fc2 --- /dev/null +++ b/text/gstr/gstr_case.go @@ -0,0 +1,142 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. +// +// | Function | Result | +// |-----------------------------------|--------------------| +// | SnakeCase(s) | any_kind_of_string | +// | SnakeScreamingCase(s) | ANY_KIND_OF_STRING | +// | KebabCase(s) | any-kind-of-string | +// | KebabScreamingCase(s) | ANY-KIND-OF-STRING | +// | DelimitedCase(s, '.') | any.kind.of.string | +// | DelimitedScreamingCase(s, '.') | ANY.KIND.OF.STRING | +// | CamelCase(s) | AnyKindOfString | +// | CamelLowerCase(s) | anyKindOfString | + +package gstr + +import ( + "regexp" + "strings" +) + +var ( + numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`) + numberReplacement = []byte(`$1 $2 $3`) +) + +// CamelCase converts a string to CamelCase. +func CamelCase(s string) string { + return toCamelInitCase(s, true) +} + +// CamelLowerCase converts a string to lowerCamelCase. +func CamelLowerCase(s string) string { + if s == "" { + return s + } + if r := rune(s[0]); r >= 'A' && r <= 'Z' { + s = strings.ToLower(string(r)) + s[1:] + } + return toCamelInitCase(s, false) +} + +// SnakeCase converts a string to snake_case. +func SnakeCase(s string) string { + return DelimitedCase(s, '_') +} + +// SnakeScreamingCase converts a string to SNAKE_CASE_SCREAMING. +func SnakeScreamingCase(s string) string { + return DelimitedScreamingCase(s, '_', true) +} + +// KebabCase converts a string to kebab-case +func KebabCase(s string) string { + return DelimitedCase(s, '-') +} + +// KebabScreamingCase converts a string to KEBAB-CASE-SCREAMING. +func KebabScreamingCase(s string) string { + return DelimitedScreamingCase(s, '-', true) +} + +// DelimitedCase converts a string to snake.case.delimited. +func DelimitedCase(s string, del uint8) string { + return DelimitedScreamingCase(s, del, false) +} + +// DelimitedScreamingCase converts a string to DELIMITED.SCREAMING.CASE or delimited.screaming.case. +func DelimitedScreamingCase(s string, del uint8, screaming bool) string { + s = addWordBoundariesToNumbers(s) + s = strings.Trim(s, " ") + n := "" + for i, v := range s { + // treat acronyms as words, eg for JSONData -> JSON is a whole word + nextCaseIsChanged := false + if i+1 < len(s) { + next := s[i+1] + if (v >= 'A' && v <= 'Z' && next >= 'a' && next <= 'z') || (v >= 'a' && v <= 'z' && next >= 'A' && next <= 'Z') { + nextCaseIsChanged = true + } + } + + if i > 0 && n[len(n)-1] != del && nextCaseIsChanged { + // add underscore if next letter case type is changed + if v >= 'A' && v <= 'Z' { + n += string(del) + string(v) + } else if v >= 'a' && v <= 'z' { + n += string(v) + string(del) + } + } else if v == ' ' || v == '_' || v == '-' { + // replace spaces/underscores with delimiters + n += string(del) + } else { + n = n + string(v) + } + } + + if screaming { + n = strings.ToUpper(n) + } else { + n = strings.ToLower(n) + } + return n +} + +func addWordBoundariesToNumbers(s string) string { + b := []byte(s) + b = numberSequence.ReplaceAll(b, numberReplacement) + return string(b) +} + +// Converts a string to CamelCase +func toCamelInitCase(s string, initCase bool) string { + s = addWordBoundariesToNumbers(s) + s = strings.Trim(s, " ") + n := "" + capNext := initCase + for _, v := range s { + if v >= 'A' && v <= 'Z' { + n += string(v) + } + if v >= '0' && v <= '9' { + n += string(v) + } + if v >= 'a' && v <= 'z' { + if capNext { + n += strings.ToUpper(string(v)) + } else { + n += string(v) + } + } + if v == '_' || v == ' ' || v == '-' { + capNext = true + } else { + capNext = false + } + } + return n +} diff --git a/text/gstr/gstr_z_unit_case_test.go b/text/gstr/gstr_z_unit_case_test.go new file mode 100644 index 000000000..b401e9c66 --- /dev/null +++ b/text/gstr/gstr_z_unit_case_test.go @@ -0,0 +1,171 @@ +// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +// go test *.go -bench=".*" + +package gstr_test + +import ( + "testing" + + "github.com/gogf/gf/text/gstr" +) + +func Test_CamelCase(t *testing.T) { + cases := [][]string{ + {"test_case", "TestCase"}, + {"test", "Test"}, + {"TestCase", "TestCase"}, + {" test case ", "TestCase"}, + {"", ""}, + {"many_many_words", "ManyManyWords"}, + {"AnyKind of_string", "AnyKindOfString"}, + {"odd-fix", "OddFix"}, + {"numbers2And55with000", "Numbers2And55With000"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.CamelCase(in) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +} + +func Test_CamelLowerCase(t *testing.T) { + cases := [][]string{ + {"foo-bar", "fooBar"}, + {"TestCase", "testCase"}, + {"", ""}, + {"AnyKind of_string", "anyKindOfString"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.CamelLowerCase(in) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +} + +func Test_SnakeCase(t *testing.T) { + cases := [][]string{ + {"testCase", "test_case"}, + {"TestCase", "test_case"}, + {"Test Case", "test_case"}, + {" Test Case", "test_case"}, + {"Test Case ", "test_case"}, + {" Test Case ", "test_case"}, + {"test", "test"}, + {"test_case", "test_case"}, + {"Test", "test"}, + {"", ""}, + {"ManyManyWords", "many_many_words"}, + {"manyManyWords", "many_many_words"}, + {"AnyKind of_string", "any_kind_of_string"}, + {"numbers2and55with000", "numbers_2_and_55_with_000"}, + {"JSONData", "json_data"}, + {"userID", "user_id"}, + {"AAAbbb", "aa_abbb"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.SnakeCase(in) + if result != out { + t.Error("'" + in + "'('" + result + "' != '" + out + "')") + } + } +} + +func Test_DelimitedCase(t *testing.T) { + cases := [][]string{ + {"testCase", "test@case"}, + {"TestCase", "test@case"}, + {"Test Case", "test@case"}, + {" Test Case", "test@case"}, + {"Test Case ", "test@case"}, + {" Test Case ", "test@case"}, + {"test", "test"}, + {"test_case", "test@case"}, + {"Test", "test"}, + {"", ""}, + {"ManyManyWords", "many@many@words"}, + {"manyManyWords", "many@many@words"}, + {"AnyKind of_string", "any@kind@of@string"}, + {"numbers2and55with000", "numbers@2@and@55@with@000"}, + {"JSONData", "json@data"}, + {"userID", "user@id"}, + {"AAAbbb", "aa@abbb"}, + {"test-case", "test@case"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.DelimitedCase(in, '@') + if result != out { + t.Error("'" + in + "' ('" + result + "' != '" + out + "')") + } + } +} + +func Test_SnakeScreamingCase(t *testing.T) { + cases := [][]string{ + {"testCase", "TEST_CASE"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.SnakeScreamingCase(in) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +} + +func Test_KebabCase(t *testing.T) { + cases := [][]string{ + {"testCase", "test-case"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.KebabCase(in) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +} + +func Test_KebabScreamingCase(t *testing.T) { + cases := [][]string{ + {"testCase", "TEST-CASE"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.KebabScreamingCase(in) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +} + +func Test_DelimitedScreamingCase(t *testing.T) { + cases := [][]string{ + {"testCase", "TEST.CASE"}, + } + for _, i := range cases { + in := i[0] + out := i[1] + result := gstr.DelimitedScreamingCase(in, '.', true) + if result != out { + t.Error("'" + result + "' != '" + out + "'") + } + } +}