pax_global_header00006660000000000000000000000064141627574310014524gustar00rootroot0000000000000052 comment=a06d6db0f215c2cf24e52b5d2faea1b97e1360c6 snaker-0.2.0/000077500000000000000000000000001416275743100130065ustar00rootroot00000000000000snaker-0.2.0/.github/000077500000000000000000000000001416275743100143465ustar00rootroot00000000000000snaker-0.2.0/.github/workflows/000077500000000000000000000000001416275743100164035ustar00rootroot00000000000000snaker-0.2.0/.github/workflows/test.yml000066400000000000000000000006471416275743100201140ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: go-version: [1.16.x] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v2 - name: Test run: TMPDIR=$RUNNER_TEMP go test -v ./... snaker-0.2.0/LICENSE000066400000000000000000000020741416275743100140160ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016-2021 Kenneth Shaw Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. snaker-0.2.0/README.md000066400000000000000000000022651416275743100142720ustar00rootroot00000000000000# snaker [![Go Package][gopkg]][gopkg-link] Package `snaker` provides methods to convert `CamelCase` to and from `snake_case`. Correctly recognizes common (Go idiomatic) initialisms (`HTTP`, `XML`, etc) and provides a mechanism to override/set recognized initialisms. [gopkg]: https://pkg.go.dev/badge/github.com/kenshaw/snaker.svg (Go Package) [gopkg-link]: https://pkg.go.dev/github.com/kenshaw/snaker ## Example A basic Go example: ```go // _example/example.go package main import ( "fmt" "github.com/kenshaw/snaker" ) func main() { fmt.Println("Change CamelCase -> snake_case:", snaker.CamelToSnake("AnIdentifier")) fmt.Println("Change CamelCase -> snake_case (2):", snaker.CamelToSnake("XMLHTTPACL")) fmt.Println("Change snake_case -> CamelCase:", snaker.SnakeToCamel("an_identifier")) fmt.Println("Force CamelCase:", snaker.ForceCamelIdentifier("APoorly_named_httpMethod")) fmt.Println("Force lower camelCase:", snaker.ForceLowerCamelIdentifier("APoorly_named_httpMethod")) fmt.Println("Force lower camelCase (2):", snaker.ForceLowerCamelIdentifier("XmlHttpACL")) fmt.Println("Change snake_case identifier -> CamelCase:", snaker.SnakeToCamelIdentifier("__2__xml___thing---")) } ``` snaker-0.2.0/_example/000077500000000000000000000000001416275743100146005ustar00rootroot00000000000000snaker-0.2.0/_example/example.go000066400000000000000000000013631416275743100165650ustar00rootroot00000000000000// _example/example.go package main import ( "fmt" "github.com/kenshaw/snaker" ) func main() { fmt.Println("Change CamelCase -> snake_case:", snaker.CamelToSnake("AnIdentifier")) fmt.Println("Change CamelCase -> snake_case (2):", snaker.CamelToSnake("XMLHTTPACL")) fmt.Println("Change snake_case -> CamelCase:", snaker.SnakeToCamel("an_identifier")) fmt.Println("Force CamelCase:", snaker.ForceCamelIdentifier("APoorly_named_httpMethod")) fmt.Println("Force lower camelCase:", snaker.ForceLowerCamelIdentifier("APoorly_named_httpMethod")) fmt.Println("Force lower camelCase (2):", snaker.ForceLowerCamelIdentifier("XmlHttpACL")) fmt.Println("Change snake_case identifier -> CamelCase:", snaker.SnakeToCamelIdentifier("__2__xml___thing---")) } snaker-0.2.0/go.mod000066400000000000000000000000521416275743100141110ustar00rootroot00000000000000module github.com/kenshaw/snaker go 1.16 snaker-0.2.0/initialisms.go000066400000000000000000000112311416275743100156600ustar00rootroot00000000000000package snaker import ( "fmt" "strings" "unicode" ) // Initialisms is a set of initialisms. type Initialisms struct { m map[string]string p map[string]string max int } // New creates a new set of initialisms. func New(initialisms ...string) (*Initialisms, error) { ini := &Initialisms{ m: make(map[string]string), p: make(map[string]string), } if err := ini.Add(initialisms...); err != nil { return nil, err } return ini, nil } // NewDefaultInitialisms creates a default set of initialisms. func NewDefaultInitialisms() *Initialisms { ini, err := New(CommonInitialisms()...) if err != nil { panic(err) } if err := ini.Post("IDS", "IDs"); err != nil { panic(err) } return ini } // Add adds initialisms. func (ini *Initialisms) Add(initialisms ...string) error { for _, s := range initialisms { s = strings.ToUpper(s) if len(s) < 2 { return fmt.Errorf("invalid initialism %q", s) } ini.m[s], ini.max = s, max(ini.max, len(s)) } return nil } // Post adds a key, value pair to the initialisms and post map. func (ini *Initialisms) Post(pairs ...string) error { if len(pairs)%2 != 0 { return fmt.Errorf("invalid pairs length %d", len(pairs)) } for i := 0; i < len(pairs); i += 2 { s := strings.ToUpper(pairs[i]) if s != strings.ToUpper(pairs[i+1]) { return fmt.Errorf("invalid pair %q, %q", pairs[i], pairs[i+1]) } ini.m[s] = pairs[i+1] ini.p[pairs[i+1]] = pairs[i+1] ini.max = max(ini.max, len(s)) } return nil } // CamelToSnake converts name from camel case ("AnIdentifier") to snake case // ("an_identifier"). func (ini *Initialisms) CamelToSnake(name string) string { if name == "" { return "" } s, r := "", []rune(name) var lastWasUpper, lastWasLetter, lastWasIsm, isUpper, isLetter bool for i := 0; i < len(r); { isUpper, isLetter = unicode.IsUpper(r[i]), unicode.IsLetter(r[i]) // append _ when last was not upper and not letter if (lastWasLetter && isUpper) || (lastWasIsm && isLetter) { s += "_" } // determine next to append to r var next string if ism := ini.Peek(r[i:]); ism != "" && (!lastWasUpper || lastWasIsm) { next = ism } else { next = string(r[i]) } // save for next iteration lastWasIsm, lastWasUpper, lastWasLetter = len(next) > 1, isUpper, isLetter s += next i += len(next) } return strings.ToLower(s) } // CamelToSnakeIdentifier converts name from camel case to a snake case // identifier. func (ini *Initialisms) CamelToSnakeIdentifier(name string) string { return ToIdentifier(ini.CamelToSnake(name)) } // SnakeToCamel converts name to CamelCase. func (ini *Initialisms) SnakeToCamel(name string) string { var s string for _, word := range strings.Split(name, "_") { if word == "" { continue } if u, ok := ini.m[strings.ToUpper(word)]; ok { s += u } else { s += strings.ToUpper(word[:1]) + strings.ToLower(word[1:]) } } return s } // SnakeToCamelIdentifier converts name to its CamelCase identifier (first // letter is capitalized). func (ini *Initialisms) SnakeToCamelIdentifier(name string) string { return ini.SnakeToCamel(ToIdentifier(name)) } // ForceCamelIdentifier forces name to its CamelCase specific to Go // ("AnIdentifier"). func (ini *Initialisms) ForceCamelIdentifier(name string) string { if name == "" { return "" } return ini.SnakeToCamelIdentifier(ini.CamelToSnake(name)) } // ForceLowerCamelIdentifier forces the first portion of an identifier to be // lower case ("anIdentifier"). func (ini *Initialisms) ForceLowerCamelIdentifier(name string) string { if name == "" { return "" } name = ini.CamelToSnake(name) first := strings.SplitN(name, "_", -1)[0] name = ini.SnakeToCamelIdentifier(name) return strings.ToLower(first) + name[len(first):] } // Peek returns the next longest possible initialism in r. func (ini *Initialisms) Peek(r []rune) string { // do no work if len(r) < 2 { return "" } // grab at most next maxInitialismLen uppercase characters l := min(len(r), ini.max) var z []rune for i := 0; i < l; i++ { if !unicode.IsLetter(r[i]) { break } z = append(z, r[i]) } // bail if next few characters were not uppercase. if len(z) < 2 { return "" } // determine if common initialism for i := min(ini.max, len(z)); i >= 2; i-- { r := string(z[:i]) if s, ok := ini.m[r]; ok { return s } if s, ok := ini.p[r]; ok { return s } } return "" } // IsInitialism indicates whether or not s is a registered initialism. func (ini *Initialisms) IsInitialism(s string) bool { _, ok := ini.m[strings.ToUpper(s)] return ok } // min returns the minimum of a, b. func min(a, b int) int { if a < b { return a } return b } // max returns the max of a, b. func max(a, b int) int { if a > b { return a } return b } snaker-0.2.0/snaker.go000066400000000000000000000077141416275743100146310ustar00rootroot00000000000000// Package snaker provides methods to convert CamelCase to and from snake_case. // // Correctly recognizes common (Go idiomatic) initialisms (HTTP, XML, etc) and // provides a mechanism to override/set recognized initialisms. package snaker import ( "regexp" "strings" "unicode" ) func init() { // initialize common default initialisms. DefaultInitialisms = NewDefaultInitialisms() } // CommonInitialisms returns the set of common initialisms. // // Originally built from the list in golang.org/x/lint @ 738671d. // // Note: golang.org/x/lint has since been deprecated, and some additional // initialisms have since been added. func CommonInitialisms() []string { return []string{ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTPS", "HTTP", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UID", "UI", "URI", "URL", "UTC", "UTF8", "UUID", "VM", "XML", "XMPP", "XSRF", "XSS", "YAML", } } // DefaultInitialisms is a set of default (common) initialisms. var DefaultInitialisms *Initialisms // CamelToSnake converts name from camel case ("AnIdentifier") to snake case // ("an_identifier"). func CamelToSnake(name string) string { return DefaultInitialisms.CamelToSnake(name) } // CamelToSnakeIdentifier converts name from camel case to a snake case // identifier. func CamelToSnakeIdentifier(name string) string { return DefaultInitialisms.CamelToSnakeIdentifier(name) } // SnakeToCamel converts name to CamelCase. func SnakeToCamel(name string) string { return DefaultInitialisms.SnakeToCamel(name) } // SnakeToCamelIdentifier converts name to its CamelCase identifier (first // letter is capitalized). func SnakeToCamelIdentifier(name string) string { return DefaultInitialisms.SnakeToCamelIdentifier(name) } // ForceCamelIdentifier forces name to its CamelCase specific to Go // ("AnIdentifier"). func ForceCamelIdentifier(name string) string { return DefaultInitialisms.ForceCamelIdentifier(name) } // ForceLowerCamelIdentifier forces the first portion of an identifier to be // lower case ("anIdentifier"). func ForceLowerCamelIdentifier(name string) string { return DefaultInitialisms.ForceLowerCamelIdentifier(name) } // Peek returns the next longest possible initialism in r. func Peek(r []rune) string { return DefaultInitialisms.Peek(r) } // IsInitialism indicates whether or not s is a registered initialism. func IsInitialism(s string) bool { return DefaultInitialisms.IsInitialism(s) } // IsIdentifierChar determines if ch is a valid character for a Go identifier. // // See: go/src/go/scanner/scanner.go func IsIdentifierChar(ch rune) bool { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) || '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) } // ToIdentifier cleans s so that it is usable as an identifier. // // Substitutes invalid characters with an underscore, removes any leading // numbers/underscores, and removes trailing underscores. // // Additionally collapses multiple underscores to a single underscore. // // Makes no changes to case. func ToIdentifier(s string) string { // replace bad chars with _ s = subUnderscores(strings.TrimSpace(s)) // fix 2 or more __ and remove leading numbers/underscores s = underscoreRE.ReplaceAllString(s, "_") s = leadingRE.ReplaceAllString(s, "_") // remove leading/trailing underscores s = strings.TrimLeft(s, "_") s = strings.TrimRight(s, "_") return s } // underscoreRE matches underscores. var underscoreRE = regexp.MustCompile(`_+`) // leadingRE matches leading numbers. var leadingRE = regexp.MustCompile(`^[0-9_]+`) // subUnderscores substitues underscrose in place of runes that are invalid for // Go identifiers. func subUnderscores(s string) string { var r []rune for _, c := range s { if IsIdentifierChar(c) { r = append(r, c) } else { r = append(r, '_') } } return string(r) } snaker-0.2.0/snaker_test.go000066400000000000000000000111101416275743100156510ustar00rootroot00000000000000package snaker import "testing" func TestCamelToSnake(t *testing.T) { tests := []struct { s, exp string }{ {"", ""}, {"0", "0"}, {"_", "_"}, {"-X-", "-x-"}, {"-X_", "-x_"}, {"AReallyLongName", "a_really_long_name"}, {"SomethingID", "something_id"}, {"SomethingID_", "something_id_"}, {"_SomethingID_", "_something_id_"}, {"_Something-ID_", "_something-id_"}, {"_Something-IDS_", "_something-ids_"}, {"_Something-IDs_", "_something-ids_"}, {"ACL", "acl"}, {"GPU", "g_p_u"}, {"zGPU", "z_g_p_u"}, {"GPUs", "g_p_us"}, {"!GPU*", "!g_p_u*"}, {"GpuInfo", "gpu_info"}, {"GPUInfo", "g_p_u_info"}, {"gpUInfo", "gp_ui_nfo"}, {"gpUIDNfo", "gp_uid_nfo"}, {"gpUIDnfo", "gp_uid_nfo"}, {"HTTPWriter", "http_writer"}, {"uHTTPWriter", "u_http_writer"}, {"UHTTPWriter", "u_h_t_t_p_writer"}, {"UHTTP_Writer", "u_h_t_t_p_writer"}, {"UHTTP-Writer", "u_h_t_t_p-writer"}, {"HTTPHTTP", "http_http"}, {"uHTTPHTTP", "u_http_http"}, {"uHTTPHTTPS", "u_http_https"}, {"uHTTPHTTPS*", "u_http_https*"}, {"uHTTPSUID*", "u_https_uid*"}, {"UIDuuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, {"UID-uuidUIDIDUUID", "uid-uuid_uid_id_uuid"}, {"UIDzuuidUIDIDUUID", "uid_zuuid_uid_id_uuid"}, {"UIDzUUIDUIDidUUID", "uid_z_uuid_uid_id_uuid"}, {"UIDzUUID-UIDidUUID", "uid_z_uuid-uid_id_uuid"}, } for i, test := range tests { if v := CamelToSnake(test.s); v != test.exp { t.Errorf("test %d %q expected %q, got: %q", i, test.s, test.exp, v) } } } func TestCamelToSnakeIdentifier(t *testing.T) { tests := []struct { s, exp string }{ {"", ""}, {"0", ""}, {"_", ""}, {"-X-", "x"}, {"-X_", "x"}, {"AReallyLongName", "a_really_long_name"}, {"SomethingID", "something_id"}, {"SomethingID_", "something_id"}, {"_SomethingID_", "something_id"}, {"_Something-ID_", "something_id"}, {"_Something-IDS_", "something_ids"}, {"_Something-IDs_", "something_ids"}, {"ACL", "acl"}, {"GPU", "g_p_u"}, {"zGPU", "z_g_p_u"}, {"!GPU*", "g_p_u"}, {"GpuInfo", "gpu_info"}, {"GPUInfo", "g_p_u_info"}, {"gpUInfo", "gp_ui_nfo"}, {"gpUIDNfo", "gp_uid_nfo"}, {"gpUIDnfo", "gp_uid_nfo"}, {"HTTPWriter", "http_writer"}, {"uHTTPWriter", "u_http_writer"}, {"UHTTPWriter", "u_h_t_t_p_writer"}, {"UHTTP_Writer", "u_h_t_t_p_writer"}, {"UHTTP-Writer", "u_h_t_t_p_writer"}, {"HTTPHTTP", "http_http"}, {"uHTTPHTTP", "u_http_http"}, {"uHTTPHTTPS", "u_http_https"}, {"uHTTPHTTPS*", "u_http_https"}, {"uHTTPSUID*", "u_https_uid"}, {"UIDuuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, {"UID-uuidUIDIDUUID", "uid_uuid_uid_id_uuid"}, {"UIDzuuidUIDIDUUID", "uid_zuuid_uid_id_uuid"}, {"UIDzUUIDUIDidUUID", "uid_z_uuid_uid_id_uuid"}, {"UIDzUUID-UIDidUUID", "uid_z_uuid_uid_id_uuid"}, } for i, test := range tests { if v := CamelToSnakeIdentifier(test.s); v != test.exp { t.Errorf("test %d %q expected %q, got: %q", i, test.s, test.exp, v) } } } func TestSnakeToCamel(t *testing.T) { tests := []struct { s, exp string }{ {"", ""}, {"0", "0"}, {"_", ""}, {"x_", "X"}, {"_x", "X"}, {"_x_", "X"}, {"a_really_long_name", "AReallyLongName"}, {"something_id", "SomethingID"}, {"something_ids", "SomethingIDs"}, {"acl", "ACL"}, {"acl_", "ACL"}, {"_acl", "ACL"}, {"_acl_", "ACL"}, {"_a_c_l_", "ACL"}, {"gpu_info", "GpuInfo"}, {"GPU_info", "GpuInfo"}, {"gPU_info", "GpuInfo"}, {"g_p_u_info", "GPUInfo"}, } for i, test := range tests { if v := SnakeToCamel(test.s); v != test.exp { t.Errorf("test %d %q expected %q, got: %q", i, test.s, test.exp, v) } } } func TestSnakeToCamelIdentifier(t *testing.T) { tests := []struct { s, exp string }{ {"", ""}, {"_", ""}, {"0", ""}, {"000", ""}, {"_000", ""}, {"_000", ""}, {"000_", ""}, {"_000_", ""}, {"___0--00_", ""}, {"A0", "A0"}, {"a_0", "A0"}, {"a-0", "A0"}, {"x_", "X"}, {"_x", "X"}, {"_x_", "X"}, {"a_really_long_name", "AReallyLongName"}, {"_a_really_long_name", "AReallyLongName"}, {"a_really_long_name_", "AReallyLongName"}, {"_a_really_long_name_", "AReallyLongName"}, {"something_id", "SomethingID"}, {"something-id", "SomethingID"}, {"-something-id", "SomethingID"}, {"something-id-", "SomethingID"}, {"-something-id-", "SomethingID"}, {"-something_ids-", "SomethingIDs"}, {"-something_id_s-", "SomethingIDS"}, {"g_p_u_s", "GPUS"}, {"acl", "ACL"}, {"acl_", "ACL"}, {"_acl", "ACL"}, {"_acl_", "ACL"}, {"_a_c_l_", "ACL"}, {"gpu_info", "GpuInfo"}, {"g_p_u_info", "GPUInfo"}, } for i, test := range tests { if v := SnakeToCamelIdentifier(test.s); v != test.exp { t.Errorf("test %d %q expected %q, got: %q", i, test.s, test.exp, v) } } }