├── .github └── workflows │ └── test.yml ├── .golangci.yml ├── LICENSE ├── README.md ├── example_test.go ├── go.mod ├── initialisms.go ├── snaker.go └── snaker_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Install Go 9 | uses: actions/setup-go@v5 10 | with: 11 | go-version: stable 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | - name: Test 15 | run: | 16 | go test -v ./... 17 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | - cyclop 5 | - depguard 6 | - dupl 7 | - err113 8 | - exhaustruct 9 | - exportloopref 10 | - gochecknoglobals 11 | - gochecknoinits 12 | - mnd 13 | - nlreturn 14 | - paralleltest 15 | - prealloc 16 | - revive 17 | - testpackage 18 | - varnamelen 19 | - wastedassign 20 | - wsl 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2024 Kenneth Shaw 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snaker [![Go Package][gopkg]][gopkg-link] 2 | 3 | Package `snaker` provides methods to convert `CamelCase`, `snake_case`, and 4 | `kebab-case` to and from each other. Correctly recognizes common (Go idiomatic) 5 | initialisms (`HTTP`, `XML`, etc) with the ability to override/set recognized 6 | initialisms. 7 | 8 | [gopkg]: https://pkg.go.dev/badge/github.com/kenshaw/snaker.svg "Go Package" 9 | [gopkg-link]: https://pkg.go.dev/github.com/kenshaw/snaker 10 | 11 | ## Example 12 | 13 | ```go 14 | package snaker_test 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/kenshaw/snaker" 20 | ) 21 | 22 | func Example() { 23 | fmt.Println("Change CamelCase -> snake_case:", snaker.CamelToSnake("AnIdentifier")) 24 | fmt.Println("Change CamelCase -> snake_case (2):", snaker.CamelToSnake("XMLHTTPACL")) 25 | fmt.Println("Change snake_case -> CamelCase:", snaker.SnakeToCamel("an_identifier")) 26 | fmt.Println("Force CamelCase:", snaker.ForceCamelIdentifier("APoorly_named_httpMethod")) 27 | fmt.Println("Force lower camelCase:", snaker.ForceLowerCamelIdentifier("APoorly_named_httpMethod")) 28 | fmt.Println("Force lower camelCase (2):", snaker.ForceLowerCamelIdentifier("XmlHttpACL")) 29 | fmt.Println("Change snake_case identifier -> CamelCase:", snaker.SnakeToCamelIdentifier("__2__xml___thing---")) 30 | // Output: 31 | // Change CamelCase -> snake_case: an_identifier 32 | // Change CamelCase -> snake_case (2): xml_http_acl 33 | // Change snake_case -> CamelCase: AnIdentifier 34 | // Force CamelCase: APoorlyNamedHTTPMethod 35 | // Force lower camelCase: aPoorlyNamedHTTPMethod 36 | // Force lower camelCase (2): xmlHTTPACL 37 | // Change snake_case identifier -> CamelCase: XMLThing 38 | } 39 | ``` 40 | 41 | See the [package example][example]. 42 | 43 | [example]: https://pkg.go.dev/github.com/kenshaw/snaker/#Example 44 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package snaker_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kenshaw/snaker" 7 | ) 8 | 9 | func Example() { 10 | fmt.Println("Change CamelCase -> snake_case:", snaker.CamelToSnake("AnIdentifier")) 11 | fmt.Println("Change CamelCase -> snake_case (2):", snaker.CamelToSnake("XMLHTTPACL")) 12 | fmt.Println("Change snake_case -> CamelCase:", snaker.SnakeToCamel("an_identifier")) 13 | fmt.Println("Force CamelCase:", snaker.ForceCamelIdentifier("APoorly_named_httpMethod")) 14 | fmt.Println("Force lower camelCase:", snaker.ForceLowerCamelIdentifier("APoorly_named_httpMethod")) 15 | fmt.Println("Force lower camelCase (2):", snaker.ForceLowerCamelIdentifier("XmlHttpACL")) 16 | fmt.Println("Change snake_case identifier -> CamelCase:", snaker.SnakeToCamelIdentifier("__2__xml___thing---")) 17 | // Output: 18 | // Change CamelCase -> snake_case: an_identifier 19 | // Change CamelCase -> snake_case (2): xml_http_acl 20 | // Change snake_case -> CamelCase: AnIdentifier 21 | // Force CamelCase: APoorlyNamedHTTPMethod 22 | // Force lower camelCase: aPoorlyNamedHTTPMethod 23 | // Force lower camelCase (2): xmlHTTPACL 24 | // Change snake_case identifier -> CamelCase: XMLThing 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kenshaw/snaker 2 | 3 | go 1.23 4 | -------------------------------------------------------------------------------- /initialisms.go: -------------------------------------------------------------------------------- 1 | package snaker 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | // Initialisms is a set of initialisms. 10 | type Initialisms struct { 11 | known map[string]string 12 | post map[string]string 13 | max int 14 | } 15 | 16 | // New creates a new set of initialisms. 17 | func New(initialisms ...string) (*Initialisms, error) { 18 | ini := &Initialisms{ 19 | known: make(map[string]string), 20 | post: make(map[string]string), 21 | } 22 | if err := ini.Add(initialisms...); err != nil { 23 | return nil, err 24 | } 25 | return ini, nil 26 | } 27 | 28 | // NewDefaultInitialisms creates a set of known, common initialisms. 29 | func NewDefaultInitialisms() (*Initialisms, error) { 30 | ini, err := New(CommonInitialisms()...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | var pairs []string 35 | for _, s := range CommonPlurals() { 36 | pairs = append(pairs, s+"S", s+"s") 37 | } 38 | if err := ini.Post(pairs...); err != nil { 39 | return nil, err 40 | } 41 | return ini, nil 42 | } 43 | 44 | // Add adds a known initialisms. 45 | func (ini *Initialisms) Add(initialisms ...string) error { 46 | for _, s := range initialisms { 47 | s = strings.ToUpper(s) 48 | if len(s) < 2 { 49 | return fmt.Errorf("invalid initialism %q", s) 50 | } 51 | ini.known[s], ini.max = s, max(ini.max, len(s)) 52 | } 53 | return nil 54 | } 55 | 56 | // Post adds a key, value pair to the initialisms and post map. 57 | func (ini *Initialisms) Post(pairs ...string) error { 58 | if len(pairs)%2 != 0 { 59 | return fmt.Errorf("invalid pairs length %d", len(pairs)) 60 | } 61 | for i := 0; i < len(pairs); i += 2 { 62 | s := strings.ToUpper(pairs[i]) 63 | if s != strings.ToUpper(pairs[i+1]) { 64 | return fmt.Errorf("invalid pair %q, %q", pairs[i], pairs[i+1]) 65 | } 66 | ini.known[s] = pairs[i+1] 67 | ini.post[pairs[i+1]] = pairs[i+1] 68 | ini.max = max(ini.max, len(s)) 69 | } 70 | return nil 71 | } 72 | 73 | // CamelToSnake converts name from camel case ("AnIdentifier") to snake case 74 | // ("an_identifier"). 75 | func (ini *Initialisms) CamelToSnake(name string) string { 76 | if name == "" { 77 | return "" 78 | } 79 | var s string 80 | var wasUpper, wasLetter, wasIsm, isUpper, isLetter bool 81 | for i, r, next := 0, []rune(name), ""; i < len(r); i, s = i+len(next), s+next { 82 | isUpper, isLetter = unicode.IsUpper(r[i]), unicode.IsLetter(r[i]) 83 | // append _ when last was not upper and not letter 84 | if (wasLetter && isUpper) || (wasIsm && isLetter) { 85 | s += "_" 86 | } 87 | // determine next to append to r 88 | if ism := ini.Peek(r[i:]); ism != "" && (!wasUpper || wasIsm) { 89 | next = ism 90 | } else { 91 | next = string(r[i]) 92 | } 93 | // save for next iteration 94 | wasIsm, wasUpper, wasLetter = 1 < len(next), isUpper, isLetter 95 | } 96 | return strings.ToLower(s) 97 | } 98 | 99 | // CamelToSnakeIdentifier converts name from camel case to a snake case 100 | // identifier. 101 | func (ini *Initialisms) CamelToSnakeIdentifier(name string) string { 102 | return ToIdentifier(ini.CamelToSnake(name)) 103 | } 104 | 105 | // SnakeToCamel converts name to CamelCase. 106 | func (ini *Initialisms) SnakeToCamel(name string) string { 107 | var s string 108 | for _, word := range strings.Split(name, "_") { 109 | if word == "" { 110 | continue 111 | } 112 | if u, ok := ini.known[strings.ToUpper(word)]; ok { 113 | s += u 114 | } else { 115 | s += strings.ToUpper(word[:1]) + strings.ToLower(word[1:]) 116 | } 117 | } 118 | return s 119 | } 120 | 121 | // SnakeToCamelIdentifier converts name to its CamelCase identifier (first 122 | // letter is capitalized). 123 | func (ini *Initialisms) SnakeToCamelIdentifier(name string) string { 124 | return ini.SnakeToCamel(ToIdentifier(name)) 125 | } 126 | 127 | // ForceCamelIdentifier forces name to its CamelCase specific to Go 128 | // ("AnIdentifier"). 129 | func (ini *Initialisms) ForceCamelIdentifier(name string) string { 130 | if name == "" { 131 | return "" 132 | } 133 | return ini.SnakeToCamelIdentifier(ini.CamelToSnake(name)) 134 | } 135 | 136 | // ForceLowerCamelIdentifier forces the first portion of an identifier to be 137 | // lower case ("anIdentifier"). 138 | func (ini *Initialisms) ForceLowerCamelIdentifier(name string) string { 139 | if name == "" { 140 | return "" 141 | } 142 | name = ini.CamelToSnake(name) 143 | first := strings.Split(name, "_")[0] 144 | name = ini.SnakeToCamelIdentifier(name) 145 | return strings.ToLower(first) + name[len(first):] 146 | } 147 | 148 | // Peek returns the next longest possible initialism in v. 149 | func (ini *Initialisms) Peek(r []rune) string { 150 | // do no work 151 | if len(r) < 2 { 152 | return "" 153 | } 154 | // peek next few runes, up to max length of the largest known initialism 155 | var i int 156 | for n := min(len(r), ini.max); i < n && unicode.IsLetter(r[i]); i++ { 157 | } 158 | // bail if not enough letters 159 | if i < 2 { 160 | return "" 161 | } 162 | // determine if common initialism 163 | var k string 164 | for i = min(ini.max, i+1, len(r)); i >= 2; i-- { 165 | k = string(r[:i]) 166 | if s, ok := ini.known[k]; ok { 167 | return s 168 | } 169 | if s, ok := ini.post[k]; ok { 170 | return s 171 | } 172 | } 173 | return "" 174 | } 175 | 176 | // Is indicates whether or not s is a registered initialism. 177 | func (ini *Initialisms) Is(s string) bool { 178 | _, ok := ini.known[strings.ToUpper(s)] 179 | return ok 180 | } 181 | -------------------------------------------------------------------------------- /snaker.go: -------------------------------------------------------------------------------- 1 | // Package snaker provides methods to convert CamelCase to and from snake_case. 2 | // 3 | // Correctly recognizes common (Go idiomatic) initialisms (HTTP, XML, etc) and 4 | // provides a mechanism to override/set recognized initialisms. 5 | package snaker 6 | 7 | import ( 8 | "strings" 9 | "unicode" 10 | ) 11 | 12 | // DefaultInitialisms is the set of default (common) initialisms used by the 13 | // package level conversions funcs. 14 | var DefaultInitialisms *Initialisms 15 | 16 | func init() { 17 | // initialize common default initialisms 18 | var err error 19 | if DefaultInitialisms, err = NewDefaultInitialisms(); err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | // CamelToSnake converts name from camel case ("AnIdentifier") to snake case 25 | // ("an_identifier"). 26 | func CamelToSnake(name string) string { 27 | return DefaultInitialisms.CamelToSnake(name) 28 | } 29 | 30 | // CamelToSnakeIdentifier converts name from camel case to a snake case 31 | // identifier. 32 | func CamelToSnakeIdentifier(name string) string { 33 | return DefaultInitialisms.CamelToSnakeIdentifier(name) 34 | } 35 | 36 | // SnakeToCamel converts name to CamelCase. 37 | func SnakeToCamel(name string) string { 38 | return DefaultInitialisms.SnakeToCamel(name) 39 | } 40 | 41 | // SnakeToCamelIdentifier converts name to its CamelCase identifier (first 42 | // letter is capitalized). 43 | func SnakeToCamelIdentifier(name string) string { 44 | return DefaultInitialisms.SnakeToCamelIdentifier(name) 45 | } 46 | 47 | // ForceCamelIdentifier forces name to its CamelCase specific to Go 48 | // ("AnIdentifier"). 49 | func ForceCamelIdentifier(name string) string { 50 | return DefaultInitialisms.ForceCamelIdentifier(name) 51 | } 52 | 53 | // ForceLowerCamelIdentifier forces the first portion of an identifier to be 54 | // lower case ("anIdentifier"). 55 | func ForceLowerCamelIdentifier(name string) string { 56 | return DefaultInitialisms.ForceLowerCamelIdentifier(name) 57 | } 58 | 59 | // IsInitialism indicates whether or not s is a registered initialism. 60 | func IsInitialism(s string) bool { 61 | return DefaultInitialisms.Is(s) 62 | } 63 | 64 | // ToIdentifier cleans s so that it is usable as an identifier. 65 | // 66 | // Substitutes invalid characters with an underscore, removes any leading 67 | // numbers/underscores, and removes trailing underscores. 68 | // 69 | // Additionally collapses multiple underscores to a single underscore. 70 | // 71 | // Makes no changes to case. 72 | func ToIdentifier(s string) string { 73 | return toIdent(s, '_') 74 | } 75 | 76 | // ToKebab changes s to kebab case. 77 | // 78 | // Substitutes invalid characters with a hyphen, removes any leading 79 | // numbers/hyphens, and removes trailing hyphens. 80 | // 81 | // Additionally collapses multiple hyphens to a single hyphen. 82 | // 83 | // Converts the string to lower case. 84 | func ToKebab(s string) string { 85 | return strings.ToLower(toIdent(s, '-')) 86 | } 87 | 88 | // CommonInitialisms returns the set of common initialisms. 89 | // 90 | // Originally built from the list in golang.org/x/lint @ 738671d. 91 | // 92 | // Note: golang.org/x/lint has since been deprecated, and some additional 93 | // initialisms have since been added. 94 | func CommonInitialisms() []string { 95 | return []string{ 96 | "ACL", 97 | "API", 98 | "ASCII", 99 | "CPU", 100 | "CSS", 101 | "CSV", 102 | "DNS", 103 | "EOF", 104 | "GPU", 105 | "GUID", 106 | "HTML", 107 | "HTTP", 108 | "HTTPS", 109 | "ID", 110 | "IP", 111 | "JSON", 112 | "LHS", 113 | "QPS", 114 | "RAM", 115 | "RHS", 116 | "RPC", 117 | "SLA", 118 | "SMTP", 119 | "SQL", 120 | "SSH", 121 | "TCP", 122 | "TLS", 123 | "TSV", 124 | "TTL", 125 | "UDP", 126 | "UI", 127 | "UID", 128 | "URI", 129 | "URL", 130 | "UTC", 131 | "UTF8", 132 | "UUID", 133 | "VM", 134 | "XML", 135 | "XMPP", 136 | "XSRF", 137 | "XSS", 138 | "YAML", 139 | } 140 | } 141 | 142 | // CommonPlurals returns initialisms that have a common plural of s. 143 | func CommonPlurals() []string { 144 | return []string{ 145 | "ACL", 146 | "API", 147 | "CPU", 148 | "CSV", 149 | "GPU", 150 | "GUID", 151 | "ID", 152 | "IP", 153 | "TSV", 154 | "UID", 155 | "URI", 156 | "URL", 157 | "UUID", 158 | "VM", 159 | } 160 | } 161 | 162 | // toIdent converts s to a identifier. 163 | func toIdent(s string, c rune) string { 164 | // replace bad chars with c, and compact multiple c to single 165 | s = sub(strings.TrimSpace(s), c) 166 | // remove leading numbers and c 167 | s = strings.TrimLeftFunc(s, func(r rune) bool { 168 | return unicode.IsNumber(r) || c == r 169 | }) 170 | // remove trailing c 171 | s = strings.TrimRightFunc(s, func(r rune) bool { 172 | return c == r 173 | }) 174 | return s 175 | } 176 | 177 | // sub substitutes underscrose in place of runes that are invalid for 178 | // Go identifiers. 179 | func sub(s string, c rune) string { 180 | r := []rune(s) 181 | for i, ch := range r { 182 | if !isIdentifierChar(ch) { 183 | r[i] = c 184 | } 185 | } 186 | return string(r) 187 | } 188 | 189 | // isIdentifierChar determines if ch is a valid character for a Go identifier. 190 | // 191 | // See: go/src/go/scanner/scanner.go. 192 | func isIdentifierChar(ch rune) bool { 193 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) || 194 | '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) 195 | } 196 | -------------------------------------------------------------------------------- /snaker_test.go: -------------------------------------------------------------------------------- 1 | package snaker 2 | 3 | import "testing" 4 | 5 | func TestCamelToSnake(t *testing.T) { 6 | tests := []struct { 7 | s, exp string 8 | }{ 9 | {"", ""}, 10 | {"0", "0"}, 11 | {"_", "_"}, 12 | {"-X-", "-x-"}, 13 | {"-X_", "-x_"}, 14 | {"AReallyLongName", "a_really_long_name"}, 15 | {"SomethingID", "something_id"}, 16 | {"SomethingID_", "something_id_"}, 17 | {"_SomethingID_", "_something_id_"}, 18 | {"_Something-ID_", "_something-id_"}, 19 | {"_Something-IDS_", "_something-ids_"}, 20 | {"_Something-IDs_", "_something-ids_"}, 21 | {"ACL", "acl"}, 22 | {"GPU", "gpu"}, 23 | {"zGPU", "z_gpu"}, 24 | {"GPUs", "gpus"}, 25 | {"!GPU*", "!gpu*"}, 26 | {"GpuInfo", "gpu_info"}, 27 | {"GPUInfo", "gpu_info"}, 28 | {"gpUInfo", "gp_ui_nfo"}, 29 | {"gpUIDNfo", "gp_uid_nfo"}, 30 | {"gpUIDnfo", "gp_uid_nfo"}, 31 | {"HTTPWriter", "http_writer"}, 32 | {"uHTTPWriter", "u_http_writer"}, 33 | {"UHTTPWriter", "u_h_t_t_p_writer"}, 34 | {"UHTTP_Writer", "u_h_t_t_p_writer"}, 35 | {"UHTTP-Writer", "u_h_t_t_p-writer"}, 36 | {"HTTPHTTP", "http_http"}, 37 | {"uHTTPHTTP", "u_http_http"}, 38 | {"uHTTPHTTPS", "u_http_https"}, 39 | {"uHTTPHTTPS*", "u_http_https*"}, 40 | {"uHTTPSUID*", "u_https_uid*"}, 41 | {"UIDuuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, 42 | {"UID-uuidUIDIDUUID", "uid-uuid_uid_id_uuid"}, 43 | {"UIDzuuidUIDIDUUID", "uid_zuuid_uid_id_uuid"}, 44 | {"UIDzUUIDUIDidUUID", "uid_z_uuid_uid_id_uuid"}, 45 | {"UIDzUUID-UIDidUUID", "uid_z_uuid-uid_id_uuid"}, 46 | {"sampleIDIDS", "sample_id_ids"}, 47 | } 48 | for _, test := range tests { 49 | t.Run(test.s, func(t *testing.T) { 50 | if v := CamelToSnake(test.s); v != test.exp { 51 | t.Errorf("%q expected %q, got: %q", test.s, test.exp, v) 52 | } 53 | }) 54 | } 55 | } 56 | 57 | func TestCamelToSnakeIdentifier(t *testing.T) { 58 | tests := []struct { 59 | s, exp string 60 | }{ 61 | {"", ""}, 62 | {"0", ""}, 63 | {"_", ""}, 64 | {"-X-", "x"}, 65 | {"-X_", "x"}, 66 | {"AReallyLongName", "a_really_long_name"}, 67 | {"SomethingID", "something_id"}, 68 | {"SomethingID_", "something_id"}, 69 | {"_SomethingID_", "something_id"}, 70 | {"_Something-ID_", "something_id"}, 71 | {"_Something-IDS_", "something_ids"}, 72 | {"_Something-IDs_", "something_ids"}, 73 | {"ACL", "acl"}, 74 | {"GPU", "gpu"}, 75 | {"zGPU", "z_gpu"}, 76 | {"!GPU*", "gpu"}, 77 | {"GpuInfo", "gpu_info"}, 78 | {"GPUInfo", "gpu_info"}, 79 | {"gpUInfo", "gp_ui_nfo"}, 80 | {"gpUIDNfo", "gp_uid_nfo"}, 81 | {"gpUIDnfo", "gp_uid_nfo"}, 82 | {"HTTPWriter", "http_writer"}, 83 | {"uHTTPWriter", "u_http_writer"}, 84 | {"UHTTPWriter", "u_h_t_t_p_writer"}, 85 | {"UHTTP_Writer", "u_h_t_t_p_writer"}, 86 | {"UHTTP-Writer", "u_h_t_t_p_writer"}, 87 | {"HTTPHTTP", "http_http"}, 88 | {"uHTTPHTTP", "u_http_http"}, 89 | {"uHTTPHTTPS", "u_http_https"}, 90 | {"uHTTPHTTPS*", "u_http_https"}, 91 | {"uHTTPSUID*", "u_https_uid"}, 92 | {"UIDuuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, 93 | {"UID-uuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, 94 | {"UIDzuuidUIDIDUUID", "uid_zuuid_uid_id_uuid"}, 95 | {"UIDzUUIDUIDidUUID", "uid_z_uuid_uid_id_uuid"}, 96 | {"UIDzUUID-UIDidUUID", "uid_z_uuid_uid_id_uuid"}, 97 | {"SampleIDs", "sample_ids"}, 98 | {"SampleIDS", "sample_ids"}, 99 | {"SampleIDIDs", "sample_id_ids"}, 100 | } 101 | for _, test := range tests { 102 | t.Run(test.s, func(t *testing.T) { 103 | if v := CamelToSnakeIdentifier(test.s); v != test.exp { 104 | t.Errorf("CamelToSnake(%q) expected %q, got: %q", test.s, test.exp, v) 105 | } 106 | }) 107 | } 108 | } 109 | 110 | func TestSnakeToCamel(t *testing.T) { 111 | tests := []struct { 112 | s, exp string 113 | }{ 114 | {"", ""}, 115 | {"0", "0"}, 116 | {"_", ""}, 117 | {"x_", "X"}, 118 | {"_x", "X"}, 119 | {"_x_", "X"}, 120 | {"a_really_long_name", "AReallyLongName"}, 121 | {"a_really__long_name", "AReallyLongName"}, 122 | {"something_id", "SomethingID"}, 123 | {"something_ids", "SomethingIDs"}, 124 | {"acl", "ACL"}, 125 | {"acl_", "ACL"}, 126 | {"_acl", "ACL"}, 127 | {"_acl_", "ACL"}, 128 | {"_a_c_l_", "ACL"}, 129 | {"gpu_info", "GPUInfo"}, 130 | {"gpu_______info", "GPUInfo"}, 131 | {"GPU_info", "GPUInfo"}, 132 | {"gPU_info", "GPUInfo"}, 133 | {"g_p_u_info", "GPUInfo"}, 134 | {"uuid_id_uuid", "UUIDIDUUID"}, 135 | {"sample_id_ids", "SampleIDIDs"}, 136 | } 137 | for _, test := range tests { 138 | t.Run(test.s, func(t *testing.T) { 139 | if v := SnakeToCamel(test.s); v != test.exp { 140 | t.Errorf("SnakeToCamel(%q) expected %q, got: %q", test.s, test.exp, v) 141 | } 142 | }) 143 | } 144 | } 145 | 146 | func TestSnakeToCamelIdentifier(t *testing.T) { 147 | tests := []struct { 148 | s, exp string 149 | }{ 150 | {"", ""}, 151 | {"_", ""}, 152 | {"0", ""}, 153 | {"000", ""}, 154 | {"_000", ""}, 155 | {"_000", ""}, 156 | {"000_", ""}, 157 | {"_000_", ""}, 158 | {"___0--00_", ""}, 159 | {"A0", "A0"}, 160 | {"a_0", "A0"}, 161 | {"a-0", "A0"}, 162 | {"x_", "X"}, 163 | {"_x", "X"}, 164 | {"_x_", "X"}, 165 | {"a_really_long_name", "AReallyLongName"}, 166 | {"_a_really_long_name", "AReallyLongName"}, 167 | {"a_really_long_name_", "AReallyLongName"}, 168 | {"_a_really_long_name_", "AReallyLongName"}, 169 | {"_a_really___long_name_", "AReallyLongName"}, 170 | {"something_id", "SomethingID"}, 171 | {"something-id", "SomethingID"}, 172 | {"-something-id", "SomethingID"}, 173 | {"something-id-", "SomethingID"}, 174 | {"-something-id-", "SomethingID"}, 175 | {"-something_ids-", "SomethingIDs"}, 176 | {"-something_id_s-", "SomethingIDS"}, 177 | {"g_p_u_s", "GPUS"}, 178 | {"gpus", "GPUs"}, 179 | {"acl", "ACL"}, 180 | {"acls", "ACLs"}, 181 | {"acl_", "ACL"}, 182 | {"_acl", "ACL"}, 183 | {"_acl_", "ACL"}, 184 | {"_a_c_l_", "ACL"}, 185 | {"gpu_info", "GPUInfo"}, 186 | {"g_p_u_info", "GPUInfo"}, 187 | {"uuid_id_uuid", "UUIDIDUUID"}, 188 | {"sample_id_ids", "SampleIDIDs"}, 189 | } 190 | for _, test := range tests { 191 | t.Run(test.s, func(t *testing.T) { 192 | if v := SnakeToCamelIdentifier(test.s); v != test.exp { 193 | t.Errorf("SnakeToCamelIdentifier(%q) expected %q, got: %q", test.s, test.exp, v) 194 | } 195 | }) 196 | } 197 | } 198 | --------------------------------------------------------------------------------