├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── kace.go ├── kace_bench_test.go ├── kace_example_test.go ├── kace_func_test.go ├── kace_unit_test.go └── ktrie └── ktrie.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 codemodus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kace 2 | 3 | go get "github.com/codemodus/kace" 4 | 5 | Package kace provides common case conversion functions which take into 6 | consideration common initialisms. 7 | 8 | ## Usage 9 | 10 | ```go 11 | func Camel(s string) string 12 | func Kebab(s string) string 13 | func KebabUpper(s string) string 14 | func Pascal(s string) string 15 | func Snake(s string) string 16 | func SnakeUpper(s string) string 17 | type Kace 18 | func New(initialisms map[string]bool) (*Kace, error) 19 | func (k *Kace) Camel(s string) string 20 | func (k *Kace) Kebab(s string) string 21 | func (k *Kace) KebabUpper(s string) string 22 | func (k *Kace) Pascal(s string) string 23 | func (k *Kace) Snake(s string) string 24 | func (k *Kace) SnakeUpper(s string) string 25 | ``` 26 | 27 | ### Setup 28 | 29 | ```go 30 | import ( 31 | "fmt" 32 | 33 | "github.com/codemodus/kace" 34 | ) 35 | 36 | func main() { 37 | s := "this is a test sql." 38 | 39 | fmt.Println(kace.Camel(s)) 40 | fmt.Println(kace.Pascal(s)) 41 | 42 | fmt.Println(kace.Snake(s)) 43 | fmt.Println(kace.SnakeUpper(s)) 44 | 45 | fmt.Println(kace.Kebab(s)) 46 | fmt.Println(kace.KebabUpper(s)) 47 | 48 | customInitialisms := map[string]bool{ 49 | "THIS": true, 50 | } 51 | k, err := kace.New(customInitialisms) 52 | if err != nil { 53 | // handle error 54 | } 55 | 56 | fmt.Println(k.Camel(s)) 57 | fmt.Println(k.Pascal(s)) 58 | 59 | fmt.Println(k.Snake(s)) 60 | fmt.Println(k.SnakeUpper(s)) 61 | 62 | fmt.Println(k.Kebab(s)) 63 | fmt.Println(k.KebabUpper(s)) 64 | 65 | // Output: 66 | // thisIsATestSQL 67 | // ThisIsATestSQL 68 | // this_is_a_test_sql 69 | // THIS_IS_A_TEST_SQL 70 | // this-is-a-test-sql 71 | // THIS-IS-A-TEST-SQL 72 | // thisIsATestSql 73 | // THISIsATestSql 74 | // this_is_a_test_sql 75 | // THIS_IS_A_TEST_SQL 76 | // this-is-a-test-sql 77 | // THIS-IS-A-TEST-SQL 78 | } 79 | ``` 80 | 81 | ## More Info 82 | 83 | ### TODO 84 | 85 | #### Test Trie 86 | 87 | Test the current trie. 88 | 89 | ## Documentation 90 | 91 | View the [GoDoc](http://godoc.org/github.com/codemodus/kace) 92 | 93 | ## Benchmarks 94 | 95 | benchmark iter time/iter bytes alloc allocs 96 | --------- ---- --------- ----------- ------ 97 | BenchmarkCamel4 2000000 947.00 ns/op 112 B/op 3 allocs/op 98 | BenchmarkSnake4 2000000 696.00 ns/op 128 B/op 2 allocs/op 99 | BenchmarkSnakeUpper4 2000000 679.00 ns/op 128 B/op 2 allocs/op 100 | BenchmarkKebab4 2000000 691.00 ns/op 128 B/op 2 allocs/op 101 | BenchmarkKebabUpper4 2000000 677.00 ns/op 128 B/op 2 allocs/op 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/codemodus/kace 2 | -------------------------------------------------------------------------------- /kace.go: -------------------------------------------------------------------------------- 1 | // Package kace provides common case conversion functions which take into 2 | // consideration common initialisms. 3 | package kace 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "unicode" 9 | 10 | "github.com/codemodus/kace/ktrie" 11 | ) 12 | 13 | const ( 14 | kebabDelim = '-' 15 | snakeDelim = '_' 16 | none = rune(-1) 17 | ) 18 | 19 | var ( 20 | ciTrie *ktrie.KTrie 21 | ) 22 | 23 | func init() { 24 | var err error 25 | if ciTrie, err = ktrie.NewKTrie(ciMap); err != nil { 26 | panic(err) 27 | } 28 | } 29 | 30 | // Camel returns a camelCased string. 31 | func Camel(s string) string { 32 | return camelCase(ciTrie, s, false) 33 | } 34 | 35 | // Pascal returns a PascalCased string. 36 | func Pascal(s string) string { 37 | return camelCase(ciTrie, s, true) 38 | } 39 | 40 | // Kebab returns a kebab-cased string with all lowercase letters. 41 | func Kebab(s string) string { 42 | return delimitedCase(s, kebabDelim, false) 43 | } 44 | 45 | // KebabUpper returns a KEBAB-CASED string with all upper case letters. 46 | func KebabUpper(s string) string { 47 | return delimitedCase(s, kebabDelim, true) 48 | } 49 | 50 | // Snake returns a snake_cased string with all lowercase letters. 51 | func Snake(s string) string { 52 | return delimitedCase(s, snakeDelim, false) 53 | } 54 | 55 | // SnakeUpper returns a SNAKE_CASED string with all upper case letters. 56 | func SnakeUpper(s string) string { 57 | return delimitedCase(s, snakeDelim, true) 58 | } 59 | 60 | // Kace provides common case conversion methods which take into 61 | // consideration common initialisms set by the user. 62 | type Kace struct { 63 | t *ktrie.KTrie 64 | } 65 | 66 | // New returns a pointer to an instance of kace loaded with a common 67 | // initialsms trie based on the provided map. Before conversion to a 68 | // trie, the provided map keys are all upper cased. 69 | func New(initialisms map[string]bool) (*Kace, error) { 70 | ci := initialisms 71 | if ci == nil { 72 | ci = map[string]bool{} 73 | } 74 | 75 | ci = sanitizeCI(ci) 76 | 77 | t, err := ktrie.NewKTrie(ci) 78 | if err != nil { 79 | return nil, fmt.Errorf("kace: cannot create trie: %s", err) 80 | } 81 | 82 | k := &Kace{ 83 | t: t, 84 | } 85 | 86 | return k, nil 87 | } 88 | 89 | // Camel returns a camelCased string. 90 | func (k *Kace) Camel(s string) string { 91 | return camelCase(k.t, s, false) 92 | } 93 | 94 | // Pascal returns a PascalCased string. 95 | func (k *Kace) Pascal(s string) string { 96 | return camelCase(k.t, s, true) 97 | } 98 | 99 | // Snake returns a snake_cased string with all lowercase letters. 100 | func (k *Kace) Snake(s string) string { 101 | return delimitedCase(s, snakeDelim, false) 102 | } 103 | 104 | // SnakeUpper returns a SNAKE_CASED string with all upper case letters. 105 | func (k *Kace) SnakeUpper(s string) string { 106 | return delimitedCase(s, snakeDelim, true) 107 | } 108 | 109 | // Kebab returns a kebab-cased string with all lowercase letters. 110 | func (k *Kace) Kebab(s string) string { 111 | return delimitedCase(s, kebabDelim, false) 112 | } 113 | 114 | // KebabUpper returns a KEBAB-CASED string with all upper case letters. 115 | func (k *Kace) KebabUpper(s string) string { 116 | return delimitedCase(s, kebabDelim, true) 117 | } 118 | 119 | func camelCase(t *ktrie.KTrie, s string, ucFirst bool) string { 120 | rs := []rune(s) 121 | offset := 0 122 | prev := none 123 | 124 | for i := 0; i < len(rs); i++ { 125 | r := rs[i] 126 | 127 | switch { 128 | case unicode.IsLetter(r): 129 | ucCurr := isToBeUpper(r, prev, ucFirst) 130 | 131 | if ucCurr || isSegmentStart(r, prev) { 132 | prv, skip := updateRunes(rs, i, offset, t, ucCurr) 133 | if skip > 0 { 134 | i += skip - 1 135 | prev = prv 136 | continue 137 | } 138 | } 139 | 140 | prev = updateRune(rs, i, offset, ucCurr) 141 | continue 142 | 143 | case unicode.IsNumber(r): 144 | prev = updateRune(rs, i, offset, false) 145 | continue 146 | 147 | default: 148 | prev = r 149 | offset-- 150 | } 151 | } 152 | 153 | return string(rs[:len(rs)+offset]) 154 | } 155 | 156 | func isToBeUpper(curr, prev rune, ucFirst bool) bool { 157 | if prev == none { 158 | return ucFirst 159 | } 160 | 161 | return isSegmentStart(curr, prev) 162 | } 163 | 164 | func isSegmentStart(curr, prev rune) bool { 165 | if !unicode.IsLetter(prev) || unicode.IsUpper(curr) && unicode.IsLower(prev) { 166 | return true 167 | } 168 | 169 | return false 170 | } 171 | 172 | func updateRune(rs []rune, i, offset int, upper bool) rune { 173 | r := rs[i] 174 | 175 | dest := i + offset 176 | if dest < 0 || i > len(rs)-1 { 177 | panic("this function has been used or designed incorrectly") 178 | } 179 | 180 | fn := unicode.ToLower 181 | if upper { 182 | fn = unicode.ToUpper 183 | } 184 | 185 | rs[dest] = fn(r) 186 | 187 | return r 188 | } 189 | 190 | func updateRunes(rs []rune, i, offset int, t *ktrie.KTrie, upper bool) (rune, int) { 191 | r := rs[i] 192 | ns := nextSegment(rs, i) 193 | ct := len(ns) 194 | 195 | if ct < t.MinDepth() || ct > t.MaxDepth() || !t.FindAsUpper(ns) { 196 | return r, 0 197 | } 198 | 199 | for j := i; j < i+ct; j++ { 200 | r = updateRune(rs, j, offset, upper) 201 | } 202 | 203 | return r, ct 204 | } 205 | 206 | func nextSegment(rs []rune, i int) []rune { 207 | for j := i; j < len(rs); j++ { 208 | if !unicode.IsLetter(rs[j]) && !unicode.IsNumber(rs[j]) { 209 | return rs[i:j] 210 | } 211 | 212 | if j == len(rs)-1 { 213 | return rs[i : j+1] 214 | } 215 | } 216 | 217 | return nil 218 | } 219 | 220 | func delimitedCase(s string, delim rune, upper bool) string { 221 | buf := make([]rune, 0, len(s)*2) 222 | 223 | for i := len(s); i > 0; i-- { 224 | switch { 225 | case unicode.IsLetter(rune(s[i-1])): 226 | if i < len(s) && unicode.IsUpper(rune(s[i])) { 227 | if i > 1 && unicode.IsLower(rune(s[i-1])) || i < len(s)-2 && unicode.IsLower(rune(s[i+1])) { 228 | buf = append(buf, delim) 229 | } 230 | } 231 | 232 | buf = appendCased(buf, upper, rune(s[i-1])) 233 | 234 | case unicode.IsNumber(rune(s[i-1])): 235 | if i == len(s) || i == 1 || unicode.IsNumber(rune(s[i])) { 236 | buf = append(buf, rune(s[i-1])) 237 | continue 238 | } 239 | 240 | buf = append(buf, delim, rune(s[i-1])) 241 | 242 | default: 243 | if i == len(s) { 244 | continue 245 | } 246 | 247 | buf = append(buf, delim) 248 | } 249 | } 250 | 251 | reverse(buf) 252 | 253 | return string(buf) 254 | } 255 | 256 | func appendCased(rs []rune, upper bool, r rune) []rune { 257 | if upper { 258 | rs = append(rs, unicode.ToUpper(r)) 259 | return rs 260 | } 261 | 262 | rs = append(rs, unicode.ToLower(r)) 263 | 264 | return rs 265 | } 266 | 267 | func reverse(s []rune) { 268 | for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 269 | s[i], s[j] = s[j], s[i] 270 | } 271 | } 272 | 273 | var ( 274 | // github.com/golang/lint/blob/master/lint.go 275 | ciMap = map[string]bool{ 276 | "ACL": true, 277 | "API": true, 278 | "ASCII": true, 279 | "CPU": true, 280 | "CSS": true, 281 | "DNS": true, 282 | "EOF": true, 283 | "GUID": true, 284 | "HTML": true, 285 | "HTTP": true, 286 | "HTTPS": true, 287 | "ID": true, 288 | "IP": true, 289 | "JSON": true, 290 | "LHS": true, 291 | "QPS": true, 292 | "RAM": true, 293 | "RHS": true, 294 | "RPC": true, 295 | "SLA": true, 296 | "SMTP": true, 297 | "SQL": true, 298 | "SSH": true, 299 | "TCP": true, 300 | "TLS": true, 301 | "TTL": true, 302 | "UDP": true, 303 | "UI": true, 304 | "UID": true, 305 | "UUID": true, 306 | "URI": true, 307 | "URL": true, 308 | "UTF8": true, 309 | "VM": true, 310 | "XML": true, 311 | "XMPP": true, 312 | "XSRF": true, 313 | "XSS": true, 314 | } 315 | ) 316 | 317 | func sanitizeCI(m map[string]bool) map[string]bool { 318 | r := map[string]bool{} 319 | 320 | for k := range m { 321 | fn := func(r rune) rune { 322 | if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 323 | return -1 324 | } 325 | return r 326 | } 327 | 328 | k = strings.Map(fn, k) 329 | k = strings.ToUpper(k) 330 | 331 | if k == "" { 332 | continue 333 | } 334 | 335 | r[k] = true 336 | } 337 | 338 | return r 339 | } 340 | -------------------------------------------------------------------------------- /kace_bench_test.go: -------------------------------------------------------------------------------- 1 | package kace 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkCamel4(b *testing.B) { 8 | for n := 0; n < b.N; n++ { 9 | _ = Camel("this_is_a_test") 10 | } 11 | } 12 | 13 | func BenchmarkPascal4(b *testing.B) { 14 | for n := 0; n < b.N; n++ { 15 | _ = Pascal("this_is_a_test") 16 | } 17 | } 18 | 19 | func BenchmarkSnake4(b *testing.B) { 20 | for n := 0; n < b.N; n++ { 21 | _ = Snake("ThisIsATest") 22 | } 23 | } 24 | 25 | func BenchmarkSnakeUpper4(b *testing.B) { 26 | for n := 0; n < b.N; n++ { 27 | _ = SnakeUpper("ThisIsATest") 28 | } 29 | } 30 | 31 | func BenchmarkKebab4(b *testing.B) { 32 | for n := 0; n < b.N; n++ { 33 | _ = Kebab("ThisIsATest") 34 | } 35 | } 36 | 37 | func BenchmarkKebabUpper4(b *testing.B) { 38 | for n := 0; n < b.N; n++ { 39 | _ = KebabUpper("ThisIsATest") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /kace_example_test.go: -------------------------------------------------------------------------------- 1 | package kace_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/codemodus/kace" 7 | ) 8 | 9 | func Example() { 10 | s := "this is a test sql." 11 | 12 | fmt.Println(kace.Camel(s)) 13 | fmt.Println(kace.Pascal(s)) 14 | 15 | fmt.Println(kace.Snake(s)) 16 | fmt.Println(kace.SnakeUpper(s)) 17 | 18 | fmt.Println(kace.Kebab(s)) 19 | fmt.Println(kace.KebabUpper(s)) 20 | 21 | customInitialisms := map[string]bool{ 22 | "THIS": true, 23 | } 24 | k, err := kace.New(customInitialisms) 25 | if err != nil { 26 | // handle error 27 | } 28 | 29 | fmt.Println(k.Camel(s)) 30 | fmt.Println(k.Pascal(s)) 31 | 32 | fmt.Println(k.Snake(s)) 33 | fmt.Println(k.SnakeUpper(s)) 34 | 35 | fmt.Println(k.Kebab(s)) 36 | fmt.Println(k.KebabUpper(s)) 37 | 38 | // Output: 39 | // thisIsATestSQL 40 | // ThisIsATestSQL 41 | // this_is_a_test_sql 42 | // THIS_IS_A_TEST_SQL 43 | // this-is-a-test-sql 44 | // THIS-IS-A-TEST-SQL 45 | // thisIsATestSql 46 | // THISIsATestSql 47 | // this_is_a_test_sql 48 | // THIS_IS_A_TEST_SQL 49 | // this-is-a-test-sql 50 | // THIS-IS-A-TEST-SQL 51 | } 52 | -------------------------------------------------------------------------------- /kace_func_test.go: -------------------------------------------------------------------------------- 1 | package kace_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/codemodus/kace" 7 | ) 8 | 9 | var ( 10 | altCIMap = map[string]bool{ 11 | "THIS": true, 12 | } 13 | ) 14 | 15 | func TestFuncPascal(t *testing.T) { 16 | var data = []struct { 17 | i string 18 | o string 19 | }{ 20 | {"This is a test sql", "ThisIsATestSQL"}, 21 | {"this is a test sql", "ThisIsATestSQL"}, 22 | } 23 | 24 | for _, v := range data { 25 | want := v.o 26 | got := kace.Pascal(v.i) 27 | if got != want { 28 | t.Errorf("got %v, want %v", got, want) 29 | } 30 | } 31 | } 32 | 33 | func TestFuncCamel(t *testing.T) { 34 | var data = []struct { 35 | i string 36 | o string 37 | }{ 38 | {"this is a test sql", "thisIsATestSQL"}, 39 | {"this_is_a_test sql", "thisIsATestSQL"}, 40 | } 41 | 42 | for _, v := range data { 43 | want := v.o 44 | got := kace.Camel(v.i) 45 | if got != want { 46 | t.Errorf("got %v, want %v", got, want) 47 | } 48 | } 49 | } 50 | 51 | func TestFuncSnake(t *testing.T) { 52 | var data = []struct { 53 | i string 54 | o string 55 | }{ 56 | {"thisIsATestSQL", "this_is_a_test_sql"}, 57 | {"ThisIsATestSQL", "this_is_a_test_sql"}, 58 | } 59 | 60 | for _, v := range data { 61 | want := v.o 62 | got := kace.Snake(v.i) 63 | if got != want { 64 | t.Errorf("got %v, want %v", got, want) 65 | } 66 | } 67 | } 68 | 69 | func TestFuncSnakeUpper(t *testing.T) { 70 | var data = []struct { 71 | i string 72 | o string 73 | }{ 74 | {"thisIsATestSQL", "THIS_IS_A_TEST_SQL"}, 75 | {"ThisIsATestSQL", "THIS_IS_A_TEST_SQL"}, 76 | } 77 | 78 | for _, v := range data { 79 | want := v.o 80 | got := kace.SnakeUpper(v.i) 81 | if got != want { 82 | t.Errorf("got %v, want %v", got, want) 83 | } 84 | } 85 | } 86 | 87 | func TestFuncKebab(t *testing.T) { 88 | var data = []struct { 89 | i string 90 | o string 91 | }{ 92 | {"thisIsATestSQL", "this-is-a-test-sql"}, 93 | {"ThisIsATestSQL", "this-is-a-test-sql"}, 94 | } 95 | 96 | for _, v := range data { 97 | want := v.o 98 | got := kace.Kebab(v.i) 99 | if got != want { 100 | t.Errorf("got %v, want %v", got, want) 101 | } 102 | } 103 | } 104 | 105 | func TestFuncKebabUpper(t *testing.T) { 106 | var data = []struct { 107 | i string 108 | o string 109 | }{ 110 | {"thisIsATestSQL", "THIS-IS-A-TEST-SQL"}, 111 | {"ThisIsATestSQL", "THIS-IS-A-TEST-SQL"}, 112 | } 113 | 114 | for _, v := range data { 115 | want := v.o 116 | got := kace.KebabUpper(v.i) 117 | if got != want { 118 | t.Errorf("got %v, want %v", got, want) 119 | } 120 | } 121 | } 122 | 123 | func TestFuncKacePascal(t *testing.T) { 124 | var data = []struct { 125 | i string 126 | o string 127 | }{ 128 | {"This is a test sql", "THISIsATestSql"}, 129 | {"this is a test sql", "THISIsATestSql"}, 130 | } 131 | 132 | k, _ := kace.New(altCIMap) 133 | 134 | for _, v := range data { 135 | want := v.o 136 | got := k.Pascal(v.i) 137 | if got != want { 138 | t.Errorf("got %v, want %v", got, want) 139 | } 140 | } 141 | } 142 | 143 | func TestFuncKaceCamel(t *testing.T) { 144 | var data = []struct { 145 | i string 146 | o string 147 | }{ 148 | {"this is a test sql", "thisIsATestSql"}, 149 | {"this_is_a_test sql", "thisIsATestSql"}, 150 | } 151 | 152 | k, _ := kace.New(altCIMap) 153 | 154 | for _, v := range data { 155 | want := v.o 156 | got := k.Camel(v.i) 157 | if got != want { 158 | t.Errorf("got %v, want %v", got, want) 159 | } 160 | } 161 | } 162 | 163 | func TestFuncKaceSnake(t *testing.T) { 164 | var data = []struct { 165 | i string 166 | o string 167 | }{ 168 | {"thisIsATestSQL", "this_is_a_test_sql"}, 169 | {"ThisIsATestSQL", "this_is_a_test_sql"}, 170 | } 171 | 172 | k, _ := kace.New(altCIMap) 173 | 174 | for _, v := range data { 175 | want := v.o 176 | got := k.Snake(v.i) 177 | if got != want { 178 | t.Errorf("got %v, want %v", got, want) 179 | } 180 | } 181 | } 182 | 183 | func TestFuncKaceSnakeUpper(t *testing.T) { 184 | var data = []struct { 185 | i string 186 | o string 187 | }{ 188 | {"thisIsATestSQL", "THIS_IS_A_TEST_SQL"}, 189 | {"ThisIsATestSQL", "THIS_IS_A_TEST_SQL"}, 190 | } 191 | 192 | k, _ := kace.New(altCIMap) 193 | 194 | for _, v := range data { 195 | want := v.o 196 | got := k.SnakeUpper(v.i) 197 | if got != want { 198 | t.Errorf("got %v, want %v", got, want) 199 | } 200 | } 201 | } 202 | 203 | func TestFuncKaceKebab(t *testing.T) { 204 | var data = []struct { 205 | i string 206 | o string 207 | }{ 208 | {"thisIsATestSQL", "this-is-a-test-sql"}, 209 | {"ThisIsATestSQL", "this-is-a-test-sql"}, 210 | } 211 | 212 | k, _ := kace.New(altCIMap) 213 | 214 | for _, v := range data { 215 | want := v.o 216 | got := k.Kebab(v.i) 217 | if got != want { 218 | t.Errorf("got %v, want %v", got, want) 219 | } 220 | } 221 | } 222 | 223 | func TestFuncKaceKebabUpper(t *testing.T) { 224 | var data = []struct { 225 | i string 226 | o string 227 | }{ 228 | {"thisIsATestSQL", "THIS-IS-A-TEST-SQL"}, 229 | {"ThisIsATestSQL", "THIS-IS-A-TEST-SQL"}, 230 | } 231 | 232 | k, _ := kace.New(altCIMap) 233 | 234 | for _, v := range data { 235 | want := v.o 236 | got := k.KebabUpper(v.i) 237 | if got != want { 238 | t.Errorf("got %v, want %v", got, want) 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /kace_unit_test.go: -------------------------------------------------------------------------------- 1 | package kace 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestUnitCamelCase(t *testing.T) { 9 | var pascalData = []struct { 10 | i string 11 | o string 12 | }{ 13 | {"This is a test", "ThisIsATest"}, 14 | {"this is a test", "ThisIsATest"}, 15 | {"this is 4 test", "ThisIs4Test"}, 16 | {"5this is a test", "5ThisIsATest"}, 17 | {"this_is_a_test", "ThisIsATest"}, 18 | {"This is a test.", "ThisIsATest"}, 19 | {"This.is.a.Test", "ThisIsATest"}, 20 | {"andThisToo", "AndThisToo"}, 21 | {"AndThisToo", "AndThisToo"}, 22 | {"this http conn", "ThisHTTPConn"}, 23 | {"this_https_conn", "ThisHTTPSConn"}, 24 | {"this_http_scan", "ThisHTTPScan"}, 25 | {"and willid mess it up", "AndWillidMessItUp"}, 26 | {"and willid_mess_it_up", "AndWillidMessItUp"}, 27 | {"will ident mess it up", "WillIdentMessItUp"}, 28 | {"will_ident_mess_it_up", "WillIdentMessItUp"}, 29 | {"will pident mess it up", "WillPidentMessItUp"}, 30 | {"will_pident_mess_it_up", "WillPidentMessItUp"}, 31 | {"will id mess it up", "WillIDMessItUp"}, 32 | {"will_id_mess_it_up", "WillIDMessItUp"}, 33 | {"will_utf8_mess_it_up", "WillUTF8MessItUp"}, 34 | {"willutf8_mess_it_up", "Willutf8MessItUp"}, 35 | {"http_first_upper", "HTTPFirstUpper"}, 36 | {"ahttp_upper", "AhttpUpper"}, 37 | {"THIS_IS_A_TEST", "ThisIsATest"}, 38 | } 39 | 40 | for k, v := range pascalData { 41 | want := v.o 42 | got := camelCase(ciTrie, v.i, true) 43 | if got != want { 44 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 45 | } 46 | } 47 | 48 | var camelData = []struct { 49 | i string 50 | o string 51 | }{ 52 | {"this is a test", "thisIsATest"}, 53 | {"this_is_a_test", "thisIsATest"}, 54 | {"this is a test.", "thisIsATest"}, 55 | {"this.is.a.Test", "thisIsATest"}, 56 | {"AndThisToo", "andThisToo"}, 57 | {"andThisToo", "andThisToo"}, 58 | {"this http conn", "thisHTTPConn"}, 59 | {"this_https_conn", "thisHTTPSConn"}, 60 | {"this_http_scan", "thisHTTPScan"}, 61 | {"and willid mess it up", "andWillidMessItUp"}, 62 | {"and willid_mess_it_up", "andWillidMessItUp"}, 63 | {"will ident mess it up", "willIdentMessItUp"}, 64 | {"will_ident_mess_it_up", "willIdentMessItUp"}, 65 | {"will pident mess it up", "willPidentMessItUp"}, 66 | {"will_pident_mess_it_up", "willPidentMessItUp"}, 67 | {"will id mess it up", "willIDMessItUp"}, 68 | {"will_id_mess_it_up", "willIDMessItUp"}, 69 | {"will_utf8_mess_it_up", "willUTF8MessItUp"}, 70 | {"willutf8_mess_it_up", "willutf8MessItUp"}, 71 | {"http_first_lower", "httpFirstLower"}, 72 | {"ahttp_lower", "ahttpLower"}, 73 | {"THIS_IS_A_TEST", "thisIsATest"}, 74 | } 75 | 76 | for k, v := range camelData { 77 | want := v.o 78 | got := camelCase(ciTrie, v.i, false) 79 | if got != want { 80 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 81 | } 82 | } 83 | } 84 | 85 | func TestUnitDelimitedCase(t *testing.T) { 86 | var snakeData = []struct { 87 | i string 88 | o string 89 | }{ 90 | {"thisIsATest", "this_is_a_test"}, 91 | {"ThisIsATest", "this_is_a_test"}, 92 | {"ThisIsATest3", "this_is_a_test3"}, 93 | {"ThisIs44Test", "this_is44_test"}, 94 | {"5ThisIsATest", "5this_is_a_test"}, 95 | {"this is a test", "this_is_a_test"}, 96 | {"this_is_a_test", "this_is_a_test"}, 97 | {"This is a test.", "this_is_a_test"}, 98 | {"This.is.a.Test", "this_is_a_test"}, 99 | {"thisHTTPSConn", "this_https_conn"}, 100 | {"thisHTTPScan", "this_http_scan"}, 101 | {"ThisHTTPSConn", "this_https_conn"}, 102 | {"ThisHTTPScan", "this_http_scan"}, 103 | {"willidMessItUp", "willid_mess_it_up"}, 104 | {"WillidMessItUp", "willid_mess_it_up"}, 105 | } 106 | 107 | for k, v := range snakeData { 108 | want := v.o 109 | got := delimitedCase(v.i, snakeDelim, false) 110 | if got != want { 111 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 112 | } 113 | } 114 | 115 | var snakeUpperData = []struct { 116 | i string 117 | o string 118 | }{ 119 | {"thisIsATest", "THIS_IS_A_TEST"}, 120 | {"ThisIsATest", "THIS_IS_A_TEST"}, 121 | {"ThisIsATest3", "THIS_IS_A_TEST3"}, 122 | {"ThisIs44Test", "THIS_IS44_TEST"}, 123 | {"5ThisIsATest", "5THIS_IS_A_TEST"}, 124 | {"this is a test", "THIS_IS_A_TEST"}, 125 | {"this_is_a_test", "THIS_IS_A_TEST"}, 126 | {"This is a test.", "THIS_IS_A_TEST"}, 127 | {"This.is.a.Test", "THIS_IS_A_TEST"}, 128 | {"thisHTTPSConn", "THIS_HTTPS_CONN"}, 129 | {"ThisHTTPSConn", "THIS_HTTPS_CONN"}, 130 | {"willidMessItUp", "WILLID_MESS_IT_UP"}, 131 | {"WillidMessItUp", "WILLID_MESS_IT_UP"}, 132 | } 133 | 134 | for k, v := range snakeUpperData { 135 | want := v.o 136 | got := delimitedCase(v.i, snakeDelim, true) 137 | if got != want { 138 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 139 | } 140 | } 141 | 142 | var kebabData = []struct { 143 | i string 144 | o string 145 | }{ 146 | {"thisIsATest", "this-is-a-test"}, 147 | {"ThisIsATest", "this-is-a-test"}, 148 | {"ThisIsATest3", "this-is-a-test3"}, 149 | {"ThisIs44Test", "this-is44-test"}, 150 | {"5ThisIsATest", "5this-is-a-test"}, 151 | {"this is a test", "this-is-a-test"}, 152 | {"this_is_a_test", "this-is-a-test"}, 153 | {"This is a test.", "this-is-a-test"}, 154 | {"This.is.a.Test", "this-is-a-test"}, 155 | {"thisHTTPSConn", "this-https-conn"}, 156 | {"ThisHTTPSConn", "this-https-conn"}, 157 | {"willidMessItUp", "willid-mess-it-up"}, 158 | {"WillidMessItUp", "willid-mess-it-up"}, 159 | } 160 | 161 | for k, v := range kebabData { 162 | want := v.o 163 | got := delimitedCase(v.i, kebabDelim, false) 164 | if got != want { 165 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 166 | } 167 | } 168 | 169 | var kebabUpperData = []struct { 170 | i string 171 | o string 172 | }{ 173 | {"thisIsATest", "THIS-IS-A-TEST"}, 174 | {"ThisIsATest", "THIS-IS-A-TEST"}, 175 | {"ThisIsATest3", "THIS-IS-A-TEST3"}, 176 | {"ThisIs44Test", "THIS-IS44-TEST"}, 177 | {"5ThisIsATest", "5THIS-IS-A-TEST"}, 178 | {"this is a test", "THIS-IS-A-TEST"}, 179 | {"this_is_a_test", "THIS-IS-A-TEST"}, 180 | {"This is a test.", "THIS-IS-A-TEST"}, 181 | {"This.is.a.Test", "THIS-IS-A-TEST"}, 182 | {"thisHTTPSConn", "THIS-HTTPS-CONN"}, 183 | {"ThisHTTPSConn", "THIS-HTTPS-CONN"}, 184 | {"willidMessItUp", "WILLID-MESS-IT-UP"}, 185 | {"WillidMessItUp", "WILLID-MESS-IT-UP"}, 186 | } 187 | 188 | for k, v := range kebabUpperData { 189 | want := v.o 190 | got := delimitedCase(v.i, kebabDelim, true) 191 | if got != want { 192 | t.Errorf("#%d (%s), got %v, want %v", k, v.i, got, want) 193 | } 194 | } 195 | } 196 | 197 | func TestUnitAppendCased(t *testing.T) { 198 | data := []struct { 199 | in []rune 200 | up bool 201 | apd rune 202 | out string 203 | }{ 204 | {[]rune("tes"), true, 't', "tesT"}, 205 | {[]rune("te541s"), false, 't', "te541st"}, 206 | } 207 | 208 | for k, v := range data { 209 | want := v.out 210 | got := string(appendCased(v.in, v.up, v.apd)) 211 | if got != want { 212 | t.Errorf("#%d (%s), got %v, want %v", k, string(v.in), got, want) 213 | } 214 | } 215 | } 216 | 217 | func TestUnitReverse(t *testing.T) { 218 | data := []struct { 219 | bef []rune 220 | aft string 221 | }{ 222 | {[]rune("test"), "tset"}, 223 | {[]rune("te541st"), "ts145et"}, 224 | } 225 | 226 | for k, v := range data { 227 | want := v.aft 228 | bef := string(v.bef) 229 | reverse(v.bef) 230 | got := string(v.bef) 231 | if got != want { 232 | t.Errorf("#%d (%s), got %v, want %v", k, bef, got, want) 233 | } 234 | } 235 | } 236 | 237 | func TestUnitSanitizeCI(t *testing.T) { 238 | data := []struct { 239 | in map[string]bool 240 | out map[string]bool 241 | }{ 242 | { 243 | map[string]bool{ 244 | "nsa": true, 245 | "CIA": false, 246 | "fbI": true, 247 | " ym ca ": false, 248 | " ": true, 249 | "": false, 250 | }, 251 | map[string]bool{ 252 | "NSA": true, 253 | "CIA": true, 254 | "FBI": true, 255 | "YMCA": true, 256 | }, 257 | }, 258 | {ciMap, ciMap}, 259 | } 260 | 261 | for _, v := range data { 262 | want := v.out 263 | got := sanitizeCI(v.in) 264 | if !reflect.DeepEqual(got, want) { 265 | t.Errorf("got %v, want %v", got, want) 266 | } 267 | } 268 | 269 | } 270 | 271 | func TestUnitNew(t *testing.T) { 272 | k, err := New(ciMap) 273 | if err != nil { 274 | t.Fatalf("unexpected error: %s", err) 275 | } 276 | 277 | if k == nil { 278 | t.Errorf("got nil, want *Kace") 279 | } 280 | 281 | k, err = New(nil) 282 | if err != nil { 283 | t.Fatalf("unexpected error: %s", err) 284 | } 285 | 286 | want := "TestSql" 287 | got := k.Pascal(want) 288 | if got != want { 289 | t.Errorf("got %v, want %v", got, want) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /ktrie/ktrie.go: -------------------------------------------------------------------------------- 1 | package ktrie 2 | 3 | import "unicode" 4 | 5 | // KNode ... 6 | type KNode struct { 7 | val rune 8 | end bool 9 | links []*KNode 10 | } 11 | 12 | // NewKNode ... 13 | func NewKNode(val rune) *KNode { 14 | return &KNode{ 15 | val: val, 16 | links: make([]*KNode, 0), 17 | } 18 | } 19 | 20 | // Add ... 21 | func (n *KNode) Add(rs []rune) { 22 | cur := n 23 | 24 | for k, v := range rs { 25 | link := cur.linkByVal(v) 26 | 27 | if link == nil { 28 | link = NewKNode(v) 29 | cur.links = append(cur.links, link) 30 | } 31 | 32 | if k == len(rs)-1 { 33 | link.end = true 34 | } 35 | 36 | cur = link 37 | } 38 | } 39 | 40 | // Find ... 41 | func (n *KNode) Find(rs []rune) bool { 42 | cur := n 43 | 44 | for _, v := range rs { 45 | cur = cur.linkByVal(v) 46 | 47 | if cur == nil { 48 | return false 49 | } 50 | } 51 | 52 | return cur.end 53 | } 54 | 55 | // FindAsUpper ... 56 | func (n *KNode) FindAsUpper(rs []rune) bool { 57 | cur := n 58 | 59 | for _, v := range rs { 60 | cur = cur.linkByVal(unicode.ToUpper(v)) 61 | 62 | if cur == nil { 63 | return false 64 | } 65 | } 66 | 67 | return cur.end 68 | } 69 | 70 | func (n *KNode) linkByVal(val rune) *KNode { 71 | for _, v := range n.links { 72 | if v.val == val { 73 | return v 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // KTrie ... 81 | type KTrie struct { 82 | *KNode 83 | 84 | maxDepth int 85 | minDepth int 86 | } 87 | 88 | // NewKTrie ... 89 | func NewKTrie(data map[string]bool) (*KTrie, error) { 90 | n := NewKNode(0) 91 | 92 | maxDepth := 0 93 | minDepth := 9001 94 | 95 | for k := range data { 96 | rs := []rune(k) 97 | l := len(rs) 98 | 99 | n.Add(rs) 100 | 101 | if l > maxDepth { 102 | maxDepth = l 103 | } 104 | if l < minDepth { 105 | minDepth = l 106 | } 107 | } 108 | 109 | t := &KTrie{ 110 | maxDepth: maxDepth, 111 | minDepth: minDepth, 112 | KNode: n, 113 | } 114 | 115 | return t, nil 116 | } 117 | 118 | // MaxDepth ... 119 | func (t *KTrie) MaxDepth() int { 120 | return t.maxDepth 121 | } 122 | 123 | // MinDepth ... 124 | func (t *KTrie) MinDepth() int { 125 | return t.minDepth 126 | } 127 | --------------------------------------------------------------------------------