├── .travis.yml ├── LICENSE ├── README.md ├── acronyms.go ├── camel.go ├── camel_test.go ├── concurrency_test.go ├── doc.go ├── go.mod ├── snake.go └── snake_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.10.x 5 | - 1.11.x 6 | - 1.12.x 7 | - 1.13.x 8 | - 1.14.x 9 | - 1.15.x 10 | - master 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ian Coleman 4 | Copyright (c) 2018 Ma_124, 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, Subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or Substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # strcase 2 | [![Godoc Reference](https://godoc.org/github.com/iancoleman/strcase?status.svg)](http://godoc.org/github.com/iancoleman/strcase) 3 | [![Build Status](https://travis-ci.com/iancoleman/strcase.svg)](https://travis-ci.com/iancoleman/strcase) 4 | [![Coverage](http://gocover.io/_badge/github.com/iancoleman/strcase?0)](http://gocover.io/github.com/iancoleman/strcase) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/iancoleman/strcase)](https://goreportcard.com/report/github.com/iancoleman/strcase) 6 | 7 | strcase is a go package for converting string case to various cases (e.g. [snake case](https://en.wikipedia.org/wiki/Snake_case) or [camel case](https://en.wikipedia.org/wiki/CamelCase)) to see the full conversion table below. 8 | 9 | ## Example 10 | 11 | ```go 12 | s := "AnyKind of_string" 13 | ``` 14 | 15 | | Function | Result | 16 | |-------------------------------------------|----------------------| 17 | | `ToSnake(s)` | `any_kind_of_string` | 18 | | `ToSnakeWithIgnore(s, '.')` | `any_kind.of_string` | 19 | | `ToScreamingSnake(s)` | `ANY_KIND_OF_STRING` | 20 | | `ToKebab(s)` | `any-kind-of-string` | 21 | | `ToScreamingKebab(s)` | `ANY-KIND-OF-STRING` | 22 | | `ToDelimited(s, '.')` | `any.kind.of.string` | 23 | | `ToScreamingDelimited(s, '.', '', true)` | `ANY.KIND.OF.STRING` | 24 | | `ToScreamingDelimited(s, '.', ' ', true)` | `ANY.KIND OF.STRING` | 25 | | `ToCamel(s)` | `AnyKindOfString` | 26 | | `ToLowerCamel(s)` | `anyKindOfString` | 27 | 28 | 29 | ## Install 30 | 31 | ```bash 32 | go get -u github.com/iancoleman/strcase 33 | ``` 34 | 35 | ## Custom Acronyms for ToCamel && ToLowerCamel 36 | 37 | Often times text can contain specific acronyms which you need to be handled a certain way. 38 | Out of the box `strcase` treats the string "ID" as "Id" or "id" but there is no way to cater 39 | for every case in the wild. 40 | 41 | To configure your custom acronym globally you can use the following before running any conversion 42 | 43 | ```go 44 | import ( 45 | "github.com/iancoleman/strcase" 46 | ) 47 | 48 | func init() { 49 | // results in "Api" using ToCamel("API") 50 | // results in "api" using ToLowerCamel("API") 51 | strcase.ConfigureAcronym("API", "api") 52 | 53 | // results in "PostgreSQL" using ToCamel("PostgreSQL") 54 | // results in "postgreSQL" using ToLowerCamel("PostgreSQL") 55 | strcase.ConfigureAcronym("PostgreSQL", "PostgreSQL") 56 | 57 | } 58 | 59 | ``` 60 | -------------------------------------------------------------------------------- /acronyms.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var uppercaseAcronym = sync.Map{} 8 | //"ID": "id", 9 | 10 | // ConfigureAcronym allows you to add additional words which will be considered acronyms 11 | func ConfigureAcronym(key, val string) { 12 | uppercaseAcronym.Store(key, val) 13 | } 14 | -------------------------------------------------------------------------------- /camel.go: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Ian Coleman 5 | * Copyright (c) 2018 Ma_124, 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, Subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or Substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package strcase 27 | 28 | import ( 29 | "strings" 30 | ) 31 | 32 | // Converts a string to CamelCase 33 | func toCamelInitCase(s string, initCase bool) string { 34 | s = strings.TrimSpace(s) 35 | if s == "" { 36 | return s 37 | } 38 | a, hasAcronym := uppercaseAcronym.Load(s) 39 | if hasAcronym { 40 | s = a.(string) 41 | } 42 | 43 | n := strings.Builder{} 44 | n.Grow(len(s)) 45 | capNext := initCase 46 | prevIsCap := false 47 | for i, v := range []byte(s) { 48 | vIsCap := v >= 'A' && v <= 'Z' 49 | vIsLow := v >= 'a' && v <= 'z' 50 | if capNext { 51 | if vIsLow { 52 | v += 'A' 53 | v -= 'a' 54 | } 55 | } else if i == 0 { 56 | if vIsCap { 57 | v += 'a' 58 | v -= 'A' 59 | } 60 | } else if prevIsCap && vIsCap && !hasAcronym { 61 | v += 'a' 62 | v -= 'A' 63 | } 64 | prevIsCap = vIsCap 65 | 66 | if vIsCap || vIsLow { 67 | n.WriteByte(v) 68 | capNext = false 69 | } else if vIsNum := v >= '0' && v <= '9'; vIsNum { 70 | n.WriteByte(v) 71 | capNext = true 72 | } else { 73 | capNext = v == '_' || v == ' ' || v == '-' || v == '.' 74 | } 75 | } 76 | return n.String() 77 | } 78 | 79 | // ToCamel converts a string to CamelCase 80 | func ToCamel(s string) string { 81 | return toCamelInitCase(s, true) 82 | } 83 | 84 | // ToLowerCamel converts a string to lowerCamelCase 85 | func ToLowerCamel(s string) string { 86 | return toCamelInitCase(s, false) 87 | } 88 | -------------------------------------------------------------------------------- /camel_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Ian Coleman 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, Subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or Substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package strcase 26 | 27 | import ( 28 | "testing" 29 | ) 30 | 31 | func toCamel(tb testing.TB) { 32 | cases := [][]string{ 33 | {"test_case", "TestCase"}, 34 | {"test.case", "TestCase"}, 35 | {"test", "Test"}, 36 | {"TestCase", "TestCase"}, 37 | {" test case ", "TestCase"}, 38 | {"", ""}, 39 | {"many_many_words", "ManyManyWords"}, 40 | {"AnyKind of_string", "AnyKindOfString"}, 41 | {"odd-fix", "OddFix"}, 42 | {"numbers2And55with000", "Numbers2And55With000"}, 43 | {"ID", "Id"}, 44 | {"CONSTANT_CASE", "ConstantCase"}, 45 | } 46 | for _, i := range cases { 47 | in := i[0] 48 | out := i[1] 49 | result := ToCamel(in) 50 | if result != out { 51 | tb.Errorf("%q (%q != %q)", in, result, out) 52 | } 53 | } 54 | } 55 | 56 | func TestToCamel(t *testing.T) { 57 | toCamel(t) 58 | } 59 | 60 | func BenchmarkToCamel(b *testing.B) { 61 | benchmarkCamelTest(b, toCamel) 62 | } 63 | 64 | func toLowerCamel(tb testing.TB) { 65 | cases := [][]string{ 66 | {"foo-bar", "fooBar"}, 67 | {"TestCase", "testCase"}, 68 | {"", ""}, 69 | {"AnyKind of_string", "anyKindOfString"}, 70 | {"AnyKind.of-string", "anyKindOfString"}, 71 | {"ID", "id"}, 72 | {"some string", "someString"}, 73 | {" some string", "someString"}, 74 | {"CONSTANT_CASE", "constantCase"}, 75 | } 76 | for _, i := range cases { 77 | in := i[0] 78 | out := i[1] 79 | result := ToLowerCamel(in) 80 | if result != out { 81 | tb.Errorf("%q (%q != %q)", in, result, out) 82 | } 83 | } 84 | } 85 | 86 | func TestToLowerCamel(t *testing.T) { 87 | toLowerCamel(t) 88 | } 89 | 90 | func TestCustomAcronymsToCamel(t *testing.T) { 91 | tests := []struct { 92 | name string 93 | acronymKey string 94 | acronymValue string 95 | expected string 96 | }{ 97 | { 98 | name: "API Custom Acronym", 99 | acronymKey: "API", 100 | acronymValue: "api", 101 | expected: "Api", 102 | }, 103 | { 104 | name: "ABCDACME Custom Acroynm", 105 | acronymKey: "ABCDACME", 106 | acronymValue: "AbcdAcme", 107 | expected: "AbcdAcme", 108 | }, 109 | { 110 | name: "PostgreSQL Custom Acronym", 111 | acronymKey: "PostgreSQL", 112 | acronymValue: "PostgreSQL", 113 | expected: "PostgreSQL", 114 | }, 115 | } 116 | for _, test := range tests { 117 | t.Run(test.name, func(t *testing.T) { 118 | ConfigureAcronym(test.acronymKey, test.acronymValue) 119 | if result := ToCamel(test.acronymKey); result != test.expected { 120 | t.Errorf("expected custom acronym result %s, got %s", test.expected, result) 121 | } 122 | }) 123 | } 124 | } 125 | 126 | func TestCustomAcronymsToLowerCamel(t *testing.T) { 127 | tests := []struct { 128 | name string 129 | acronymKey string 130 | acronymValue string 131 | expected string 132 | }{ 133 | { 134 | name: "API Custom Acronym", 135 | acronymKey: "API", 136 | acronymValue: "api", 137 | expected: "api", 138 | }, 139 | { 140 | name: "ABCDACME Custom Acroynm", 141 | acronymKey: "ABCDACME", 142 | acronymValue: "AbcdAcme", 143 | expected: "abcdAcme", 144 | }, 145 | { 146 | name: "PostgreSQL Custom Acronym", 147 | acronymKey: "PostgreSQL", 148 | acronymValue: "PostgreSQL", 149 | expected: "postgreSQL", 150 | }, 151 | } 152 | for _, test := range tests { 153 | t.Run(test.name, func(t *testing.T) { 154 | ConfigureAcronym(test.acronymKey, test.acronymValue) 155 | if result := ToLowerCamel(test.acronymKey); result != test.expected { 156 | t.Errorf("expected custom acronym result %s, got %s", test.expected, result) 157 | } 158 | }) 159 | } 160 | } 161 | 162 | func BenchmarkToLowerCamel(b *testing.B) { 163 | benchmarkCamelTest(b, toLowerCamel) 164 | } 165 | 166 | func benchmarkCamelTest(b *testing.B, fn func(testing.TB)) { 167 | for n := 0; n < b.N; n++ { 168 | fn(b) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /concurrency_test.go: -------------------------------------------------------------------------------- 1 | package strcase 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConcurrency(t *testing.T) { 8 | for i := 0; i < 10000; i++ { 9 | go doConvert() 10 | } 11 | 12 | } 13 | 14 | func doConvert() { 15 | var snakes = []string{"code", "exchange", "pe_ratio", "profit_margin", "updated_date"} 16 | for _, v := range snakes { 17 | _ = convertDatabaseNameToCamelCase(v) 18 | } 19 | } 20 | 21 | func convertDatabaseNameToCamelCase(d string) (s string) { 22 | ConfigureAcronym("price_book_mrq", "PriceBookMRQ") 23 | ConfigureAcronym("pe_ratio", "PERatio") 24 | ConfigureAcronym("peg_ratio", "PEGRatio") 25 | ConfigureAcronym("eps_estimate_current_year", "EPSEstimateCurrentYear") 26 | ConfigureAcronym("eps_estimate_next_year", "EPSEstimateNextYear") 27 | ConfigureAcronym("eps_estimate_next_quarter", "EPSNextQuarter") 28 | ConfigureAcronym("eps_estimate_current_quarter", "EPSEstimateCurrentQuarter") 29 | ConfigureAcronym("ebitda", "EBITDA") 30 | ConfigureAcronym("operating_margin_ttm", "OperatingMarginTTM") 31 | ConfigureAcronym("return_on_assets_ttm", "ReturnOnAssetsTTM") 32 | ConfigureAcronym("return_on_equity_ttm", "ReturnOnEquityTTM") 33 | ConfigureAcronym("revenue_ttm", "RevenueTTM") 34 | ConfigureAcronym("revenue_per_share_ttm", "RevenuePerShareTTM") 35 | ConfigureAcronym("quarterly_revenue_growth_yoy", "QuarterlyRevenueGrowthYOY") 36 | ConfigureAcronym("gross_profit_ttm", "GrossProfitTTM") 37 | ConfigureAcronym("diluted_eps_ttm", "DilutedEpsTTM") 38 | ConfigureAcronym("quarterly_earnings_growth_yoy", "QuarterlyEarningsGrowthYOY") 39 | ConfigureAcronym("two_hundred_day_ma", "TwoHundredDayMA") 40 | ConfigureAcronym("trailing_pe", "TrailingPE") 41 | ConfigureAcronym("forward_pe", "ForwardPE") 42 | ConfigureAcronym("price_sales_ttm", "PriceSalesTTM") 43 | ConfigureAcronym("price_book_mrq", "PriceBookMRQ") 44 | s = ToCamel(d) 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package strcase converts strings to various cases. See the conversion table below: 2 | // | Function | Result | 3 | // |---------------------------------|--------------------| 4 | // | ToSnake(s) | any_kind_of_string | 5 | // | ToScreamingSnake(s) | ANY_KIND_OF_STRING | 6 | // | ToKebab(s) | any-kind-of-string | 7 | // | ToScreamingKebab(s) | ANY-KIND-OF-STRING | 8 | // | ToDelimited(s, '.') | any.kind.of.string | 9 | // | ToScreamingDelimited(s, '.') | ANY.KIND.OF.STRING | 10 | // | ToCamel(s) | AnyKindOfString | 11 | // | ToLowerCamel(s) | anyKindOfString | 12 | package strcase 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iancoleman/strcase 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /snake.go: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Ian Coleman 5 | * Copyright (c) 2018 Ma_124, 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, Subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or Substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package strcase 27 | 28 | import ( 29 | "strings" 30 | ) 31 | 32 | // ToSnake converts a string to snake_case 33 | func ToSnake(s string) string { 34 | return ToDelimited(s, '_') 35 | } 36 | 37 | func ToSnakeWithIgnore(s string, ignore string) string { 38 | return ToScreamingDelimited(s, '_', ignore, false) 39 | } 40 | 41 | // ToScreamingSnake converts a string to SCREAMING_SNAKE_CASE 42 | func ToScreamingSnake(s string) string { 43 | return ToScreamingDelimited(s, '_', "", true) 44 | } 45 | 46 | // ToKebab converts a string to kebab-case 47 | func ToKebab(s string) string { 48 | return ToDelimited(s, '-') 49 | } 50 | 51 | // ToScreamingKebab converts a string to SCREAMING-KEBAB-CASE 52 | func ToScreamingKebab(s string) string { 53 | return ToScreamingDelimited(s, '-', "", true) 54 | } 55 | 56 | // ToDelimited converts a string to delimited.snake.case 57 | // (in this case `delimiter = '.'`) 58 | func ToDelimited(s string, delimiter uint8) string { 59 | return ToScreamingDelimited(s, delimiter, "", false) 60 | } 61 | 62 | // ToScreamingDelimited converts a string to SCREAMING.DELIMITED.SNAKE.CASE 63 | // (in this case `delimiter = '.'; screaming = true`) 64 | // or delimited.snake.case 65 | // (in this case `delimiter = '.'; screaming = false`) 66 | func ToScreamingDelimited(s string, delimiter uint8, ignore string, screaming bool) string { 67 | s = strings.TrimSpace(s) 68 | n := strings.Builder{} 69 | n.Grow(len(s) + 2) // nominal 2 bytes of extra space for inserted delimiters 70 | for i, v := range []byte(s) { 71 | vIsCap := v >= 'A' && v <= 'Z' 72 | vIsLow := v >= 'a' && v <= 'z' 73 | if vIsLow && screaming { 74 | v += 'A' 75 | v -= 'a' 76 | } else if vIsCap && !screaming { 77 | v += 'a' 78 | v -= 'A' 79 | } 80 | 81 | // treat acronyms as words, eg for JSONData -> JSON is a whole word 82 | if i+1 < len(s) { 83 | next := s[i+1] 84 | vIsNum := v >= '0' && v <= '9' 85 | nextIsCap := next >= 'A' && next <= 'Z' 86 | nextIsLow := next >= 'a' && next <= 'z' 87 | nextIsNum := next >= '0' && next <= '9' 88 | // add underscore if next letter case type is changed 89 | if (vIsCap && (nextIsLow || nextIsNum)) || (vIsLow && (nextIsCap || nextIsNum)) || (vIsNum && (nextIsCap || nextIsLow)) { 90 | prevIgnore := ignore != "" && i > 0 && strings.ContainsAny(string(s[i-1]), ignore) 91 | if !prevIgnore { 92 | if vIsCap && nextIsLow { 93 | if prevIsCap := i > 0 && s[i-1] >= 'A' && s[i-1] <= 'Z'; prevIsCap { 94 | n.WriteByte(delimiter) 95 | } 96 | } 97 | n.WriteByte(v) 98 | if vIsLow || vIsNum || nextIsNum { 99 | n.WriteByte(delimiter) 100 | } 101 | continue 102 | } 103 | } 104 | } 105 | 106 | if (v == ' ' || v == '_' || v == '-' || v == '.') && !strings.ContainsAny(string(v), ignore) { 107 | // replace space/underscore/hyphen/dot with delimiter 108 | n.WriteByte(delimiter) 109 | } else { 110 | n.WriteByte(v) 111 | } 112 | } 113 | 114 | return n.String() 115 | } 116 | -------------------------------------------------------------------------------- /snake_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Ian Coleman 5 | * Copyright (c) 2018 Ma_124, 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, Subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or Substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | */ 25 | 26 | package strcase 27 | 28 | import ( 29 | "testing" 30 | ) 31 | 32 | func toSnake(tb testing.TB) { 33 | cases := [][]string{ 34 | {"testCase", "test_case"}, 35 | {"TestCase", "test_case"}, 36 | {"Test Case", "test_case"}, 37 | {" Test Case", "test_case"}, 38 | {"Test Case ", "test_case"}, 39 | {" Test Case ", "test_case"}, 40 | {"test", "test"}, 41 | {"test_case", "test_case"}, 42 | {"Test", "test"}, 43 | {"", ""}, 44 | {"ManyManyWords", "many_many_words"}, 45 | {"manyManyWords", "many_many_words"}, 46 | {"AnyKind of_string", "any_kind_of_string"}, 47 | {"numbers2and55with000", "numbers_2_and_55_with_000"}, 48 | {"JSONData", "json_data"}, 49 | {"userID", "user_id"}, 50 | {"AAAbbb", "aa_abbb"}, 51 | {"1A2", "1_a_2"}, 52 | {"A1B", "a_1_b"}, 53 | {"A1A2A3", "a_1_a_2_a_3"}, 54 | {"A1 A2 A3", "a_1_a_2_a_3"}, 55 | {"AB1AB2AB3", "ab_1_ab_2_ab_3"}, 56 | {"AB1 AB2 AB3", "ab_1_ab_2_ab_3"}, 57 | {"some string", "some_string"}, 58 | {" some string", "some_string"}, 59 | } 60 | for _, i := range cases { 61 | in := i[0] 62 | out := i[1] 63 | result := ToSnake(in) 64 | if result != out { 65 | tb.Errorf("%q (%q != %q)", in, result, out) 66 | } 67 | } 68 | } 69 | 70 | func TestToSnake(t *testing.T) { toSnake(t) } 71 | 72 | func BenchmarkToSnake(b *testing.B) { 73 | benchmarkSnakeTest(b, toSnake) 74 | } 75 | 76 | func toSnakeWithIgnore(tb testing.TB) { 77 | cases := [][]string{ 78 | {"testCase", "test_case"}, 79 | {"TestCase", "test_case"}, 80 | {"Test Case", "test_case"}, 81 | {" Test Case", "test_case"}, 82 | {"Test Case ", "test_case"}, 83 | {" Test Case ", "test_case"}, 84 | {"test", "test"}, 85 | {"test_case", "test_case"}, 86 | {"Test", "test"}, 87 | {"", ""}, 88 | {"ManyManyWords", "many_many_words"}, 89 | {"manyManyWords", "many_many_words"}, 90 | {"AnyKind of_string", "any_kind_of_string"}, 91 | {"numbers2and55with000", "numbers_2_and_55_with_000"}, 92 | {"JSONData", "json_data"}, 93 | {"AwesomeActivity.UserID", "awesome_activity.user_id", "."}, 94 | {"AwesomeActivity.User.Id", "awesome_activity.user.id", "."}, 95 | {"AwesomeUsername@Awesome.Com", "awesome_username@awesome.com", ".@"}, 96 | {"lets-ignore all.of dots-and-dashes", "lets-ignore_all.of_dots-and-dashes", ".-"}, 97 | } 98 | for _, i := range cases { 99 | in := i[0] 100 | out := i[1] 101 | var ignore string 102 | ignore = "" 103 | if len(i) == 3 { 104 | ignore = i[2] 105 | } 106 | result := ToSnakeWithIgnore(in, ignore) 107 | if result != out { 108 | istr := "" 109 | if len(i) == 3 { 110 | istr = " ignoring '" + i[2] + "'" 111 | } 112 | tb.Errorf("%q (%q != %q%s)", in, result, out, istr) 113 | } 114 | } 115 | } 116 | 117 | func TestToSnakeWithIgnore(t *testing.T) { toSnakeWithIgnore(t) } 118 | 119 | func BenchmarkToSnakeWithIgnore(b *testing.B) { 120 | benchmarkSnakeTest(b, toSnakeWithIgnore) 121 | } 122 | 123 | func toDelimited(tb testing.TB) { 124 | cases := [][]string{ 125 | {"testCase", "test@case"}, 126 | {"TestCase", "test@case"}, 127 | {"Test Case", "test@case"}, 128 | {" Test Case", "test@case"}, 129 | {"Test Case ", "test@case"}, 130 | {" Test Case ", "test@case"}, 131 | {"test", "test"}, 132 | {"test_case", "test@case"}, 133 | {"Test", "test"}, 134 | {"", ""}, 135 | {"ManyManyWords", "many@many@words"}, 136 | {"manyManyWords", "many@many@words"}, 137 | {"AnyKind of_string", "any@kind@of@string"}, 138 | {"numbers2and55with000", "numbers@2@and@55@with@000"}, 139 | {"JSONData", "json@data"}, 140 | {"userID", "user@id"}, 141 | {"AAAbbb", "aa@abbb"}, 142 | {"test-case", "test@case"}, 143 | } 144 | for _, i := range cases { 145 | in := i[0] 146 | out := i[1] 147 | result := ToDelimited(in, '@') 148 | if result != out { 149 | tb.Errorf("%q (%q != %q)", in, result, out) 150 | } 151 | } 152 | } 153 | 154 | func TestToDelimited(t *testing.T) { toDelimited(t) } 155 | 156 | func BenchmarkToDelimited(b *testing.B) { 157 | benchmarkSnakeTest(b, toDelimited) 158 | } 159 | 160 | func toScreamingSnake(tb testing.TB) { 161 | cases := [][]string{ 162 | {"testCase", "TEST_CASE"}, 163 | } 164 | for _, i := range cases { 165 | in := i[0] 166 | out := i[1] 167 | result := ToScreamingSnake(in) 168 | if result != out { 169 | tb.Errorf("%q (%q != %q)", in, result, out) 170 | } 171 | } 172 | } 173 | 174 | func TestToScreamingSnake(t *testing.T) { toScreamingSnake(t) } 175 | 176 | func BenchmarkToScreamingSnake(b *testing.B) { 177 | benchmarkSnakeTest(b, toScreamingSnake) 178 | } 179 | 180 | func toKebab(tb testing.TB) { 181 | cases := [][]string{ 182 | {"testCase", "test-case"}, 183 | } 184 | for _, i := range cases { 185 | in := i[0] 186 | out := i[1] 187 | result := ToKebab(in) 188 | if result != out { 189 | tb.Errorf("%q (%q != %q)", in, result, out) 190 | } 191 | } 192 | } 193 | 194 | func TestToKebab(t *testing.T) { toKebab(t) } 195 | 196 | func BenchmarkToKebab(b *testing.B) { 197 | benchmarkSnakeTest(b, toKebab) 198 | } 199 | 200 | func toScreamingKebab(tb testing.TB) { 201 | cases := [][]string{ 202 | {"testCase", "TEST-CASE"}, 203 | } 204 | for _, i := range cases { 205 | in := i[0] 206 | out := i[1] 207 | result := ToScreamingKebab(in) 208 | if result != out { 209 | tb.Errorf("%q (%q != %q)", in, result, out) 210 | } 211 | } 212 | } 213 | 214 | func TestToScreamingKebab(t *testing.T) { toScreamingKebab(t) } 215 | 216 | func BenchmarkToScreamingKebab(b *testing.B) { 217 | benchmarkSnakeTest(b, toScreamingKebab) 218 | } 219 | 220 | func toScreamingDelimited(tb testing.TB) { 221 | cases := [][]string{ 222 | {"testCase", "TEST.CASE"}, 223 | } 224 | for _, i := range cases { 225 | in := i[0] 226 | out := i[1] 227 | result := ToScreamingDelimited(in, '.', "", true) 228 | if result != out { 229 | tb.Errorf("%q (%q != %q)", in, result, out) 230 | } 231 | } 232 | } 233 | 234 | func TestToScreamingDelimited(t *testing.T) { toScreamingDelimited(t) } 235 | 236 | func BenchmarkToScreamingDelimited(b *testing.B) { 237 | benchmarkSnakeTest(b, toScreamingDelimited) 238 | } 239 | 240 | func toScreamingDelimitedWithIgnore(tb testing.TB) { 241 | cases := [][]string{ 242 | {"AnyKind of_string", "ANY.KIND OF.STRING", ".", " "}, 243 | } 244 | for _, i := range cases { 245 | in := i[0] 246 | out := i[1] 247 | delimiter := i[2][0] 248 | ignore := i[3][0] 249 | result := ToScreamingDelimited(in, delimiter, string(ignore), true) 250 | if result != out { 251 | istr := "" 252 | if len(i) == 4 { 253 | istr = " ignoring '" + i[3] + "'" 254 | } 255 | tb.Errorf("%q (%q != %q%s)", in, result, out, istr) 256 | } 257 | } 258 | } 259 | 260 | func TestToScreamingDelimitedWithIgnore(t *testing.T) { toScreamingDelimitedWithIgnore(t) } 261 | 262 | func BenchmarkToScreamingDelimitedWithIgnore(b *testing.B) { 263 | benchmarkSnakeTest(b, toScreamingDelimitedWithIgnore) 264 | } 265 | 266 | func benchmarkSnakeTest(b *testing.B, fn func(testing.TB)) { 267 | for n := 0; n < b.N; n++ { 268 | fn(b) 269 | } 270 | } 271 | --------------------------------------------------------------------------------