Improve database to golang name mapping.

This commit is contained in:
go-jet 2019-07-03 16:27:14 +02:00
parent 3e7277015d
commit 950663dadb
19 changed files with 538 additions and 122 deletions

192
internal/3rdparty/snaker/snaker.go vendored Normal file
View file

@ -0,0 +1,192 @@
package snaker
// Package snaker provides methods to convert CamelCase names to snake_case and back.
// It considers the list of allowed initialsms used by github.com/golang/lint/golint (e.g. ID or HTTP)
import (
"strings"
"unicode"
)
// CamelToSnake converts a given string to snake case
func CamelToSnake(s string) string {
var result string
var words []string
var lastPos int
rs := []rune(s)
for i := 0; i < len(rs); i++ {
if i > 0 && unicode.IsUpper(rs[i]) {
if initialism := startsWithInitialism(s[lastPos:]); initialism != "" {
words = append(words, initialism)
i += len(initialism) - 1
lastPos = i
continue
}
words = append(words, s[lastPos:i])
lastPos = i
}
}
// append the last word
if s[lastPos:] != "" {
words = append(words, s[lastPos:])
}
for k, word := range words {
if k > 0 {
result += "_"
}
result += strings.ToLower(word)
}
return result
}
func snakeToCamel(s string, upperCase bool) string {
if len(s) == 0 {
return s
}
var result string
words := strings.Split(s, "_")
//// if there is no underscore, first try commons and then just return
//if len(words) == 1 {
// if exception := snakeToCamelExceptions[words[0]]; len(exception) > 0 {
// return exception
// }
//
// if upperCase {
// if upper := strings.ToUpper(words[0]); commonInitialisms[upper] {
// return upper
// }
// }
//
// w := []rune(s)
// if upperCase {
// w[0] = unicode.ToUpper(w[0])
// } else {
// w[0] = unicode.ToLower(w[0])
// }
//
// return string(w)
//}
for i, word := range words {
if exception := snakeToCamelExceptions[word]; len(exception) > 0 {
result += exception
continue
}
if upperCase || i > 0 {
if upper := strings.ToUpper(word); commonInitialisms[upper] {
result += upper
continue
}
}
if upperCase || i > 0 {
result += camelizeWord(word, len(words) > 1)
} else {
result += word
}
}
return result
}
func camelizeWord(word string, force bool) string {
runes := []rune(word)
for i, r := range runes {
if i == 0 {
runes[i] = unicode.ToUpper(r)
} else {
if !force && unicode.IsLower(r) { // already camelCase
return string(runes)
}
runes[i] = unicode.ToLower(r)
}
}
return string(runes)
}
// SnakeToCamel returns a string converted from snake case to uppercase
func SnakeToCamel(s string) string {
return snakeToCamel(s, true)
}
// SnakeToCamelLower returns a string converted from snake case to lowercase
func SnakeToCamelLower(s string) string {
return snakeToCamel(s, false)
}
// startsWithInitialism returns the initialism if the given string begins with it
func startsWithInitialism(s string) string {
var initialism string
// the longest initialism is 5 char, the shortest 2
for i := 1; i <= 5; i++ {
if len(s) > i-1 && commonInitialisms[s[:i]] {
initialism = s[:i]
}
}
return initialism
}
// commonInitialisms, taken from
// https://github.com/golang/lint/blob/206c0f020eba0f7fbcfbc467a5eb808037df2ed6/lint.go#L731
var commonInitialisms = map[string]bool{
"ACL": true,
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DNS": true,
"EOF": true,
"ETA": true,
"GPU": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"OS": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"UUID": true,
"URI": true,
"URL": true,
"UTF8": true,
"VM": true,
"XML": true,
"XMPP": true,
"XSRF": true,
"XSS": true,
"OAuth": true,
}
// add exceptions here for things that are not automatically convertable
var snakeToCamelExceptions = map[string]string{
"oauth": "OAuth",
}

View file

@ -0,0 +1,13 @@
package snaker
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestDb(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Snaker Suite")
}

121
internal/3rdparty/snaker/snaker_test.go vendored Normal file
View file

@ -0,0 +1,121 @@
package snaker
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Snaker", func() {
Describe("CamelToSnake test", func() {
It("should return an empty string on an empty input", func() {
Expect(CamelToSnake("")).To(Equal(""))
})
It("should work with one word", func() {
Expect(CamelToSnake("One")).To(Equal("one"))
})
It("should return an uppercase string as seperate words", func() {
Expect(CamelToSnake("ONE")).To(Equal("o_n_e"))
})
It("should return ID as lowercase", func() {
Expect(CamelToSnake("ID")).To(Equal("id"))
})
It("should work with a single lowercase character", func() {
Expect(CamelToSnake("i")).To(Equal("i"))
})
It("should work with a single uppcase character", func() {
Expect(CamelToSnake("I")).To(Equal("i"))
})
It("should return a long text as expected", func() {
Expect(CamelToSnake("ThisHasToBeConvertedCorrectlyID")).To(
Equal("this_has_to_be_converted_correctly_id"))
})
It("should return the text as expected if the initialism is in the middle", func() {
Expect(CamelToSnake("ThisIDIsFine")).To(Equal("this_id_is_fine"))
})
It("should work with long initialism", func() {
Expect(CamelToSnake("ThisHTTPSConnection")).To(Equal("this_https_connection"))
})
It("should work with multi initialisms", func() {
Expect(CamelToSnake("HelloHTTPSConnectionID")).To(Equal("hello_https_connection_id"))
})
It("sould work with concat initialisms", func() {
Expect(CamelToSnake("HTTPSID")).To(Equal("https_id"))
})
It("sould work with initialism where only certain characters are uppercase", func() {
Expect(CamelToSnake("OAuthClient")).To(Equal("oauth_client"))
})
})
Describe("SnakeToCamel test", func() {
It("should return an empty string on an empty input", func() {
Expect(SnakeToCamel("")).To(Equal(""))
})
It("should not blow up on trailing _", func() {
Expect(SnakeToCamel("potato_")).To(Equal("Potato"))
})
It("should return a snaked text as camel case", func() {
Expect(SnakeToCamel("this_has_to_be_uppercased")).To(
Equal("ThisHasToBeUppercased"))
})
It("should return a snaked text as camel case, except the word ID", func() {
Expect(SnakeToCamel("this_is_an_id")).To(Equal("ThisIsAnID"))
})
It("should return 'id' not as uppercase", func() {
Expect(SnakeToCamel("this_is_an_identifier")).To(Equal("ThisIsAnIdentifier"))
})
It("should simply work with id", func() {
Expect(SnakeToCamel("id")).To(Equal("ID"))
})
It("sould work with initialism where only certain characters are uppercase", func() {
Expect(SnakeToCamel("oauth_client")).To(Equal("OAuthClient"))
})
})
Describe("SnakeToCamelLower test", func() {
It("should return an empty string on an empty input", func() {
Ω(SnakeToCamelLower("")).To(Equal(""))
})
It("should not blow up on trailing _", func() {
Ω(SnakeToCamelLower("potato_")).To(Equal("potato"))
})
It("should return a snaked text as camel case", func() {
Ω(SnakeToCamelLower("this_has_to_be_uppercased")).To(
Equal("thisHasToBeUppercased"))
})
It("should return a snaked text as camel case, except the word ID", func() {
Ω(SnakeToCamelLower("this_is_an_id")).To(Equal("thisIsAnID"))
})
It("should return 'id' not as uppercase", func() {
Ω(SnakeToCamelLower("this_is_an_identifier")).To(Equal("thisIsAnIdentifier"))
})
It("should simply work with id", func() {
Ω(SnakeToCamelLower("id")).To(Equal("id"))
})
It("should simply work with leading id", func() {
Ω(SnakeToCamelLower("id_me_please")).To(Equal("idMePlease"))
})
})
})

23
internal/util/utils.go Normal file
View file

@ -0,0 +1,23 @@
package util
import (
"github.com/go-jet/jet/internal/3rdparty/snaker"
"strings"
)
func ToGoIdentifier(databaseIdentifier string) string {
if len(databaseIdentifier) == 0 {
return databaseIdentifier
}
databaseIdentifier = strings.ReplaceAll(databaseIdentifier, " ", "_")
databaseIdentifier = strings.ReplaceAll(databaseIdentifier, "-", "_")
return snaker.SnakeToCamel(databaseIdentifier)
}
func ToGoFileName(databaseIdentifier string) string {
databaseIdentifier = strings.ReplaceAll(databaseIdentifier, " ", "_")
databaseIdentifier = strings.ReplaceAll(databaseIdentifier, "-", "_")
return strings.ToLower(databaseIdentifier)
}

View file

@ -0,0 +1,24 @@
package util
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestToGoIdentifier(t *testing.T) {
assert.Equal(t, ToGoIdentifier("uuid"), "UUID")
assert.Equal(t, ToGoIdentifier("col1"), "Col1")
assert.Equal(t, ToGoIdentifier("PG-13"), "Pg13")
assert.Equal(t, ToGoIdentifier("13_pg"), "13Pg")
assert.Equal(t, ToGoIdentifier("mytable"), "Mytable")
assert.Equal(t, ToGoIdentifier("MYTABLE"), "Mytable")
assert.Equal(t, ToGoIdentifier("MyTaBlE"), "MyTaBlE")
assert.Equal(t, ToGoIdentifier("myTaBlE"), "MyTaBlE")
assert.Equal(t, ToGoIdentifier("my_table"), "MyTable")
assert.Equal(t, ToGoIdentifier("MY_TABLE"), "MyTable")
assert.Equal(t, ToGoIdentifier("My_Table"), "MyTable")
assert.Equal(t, ToGoIdentifier("My Table"), "MyTable")
assert.Equal(t, ToGoIdentifier("My-Table"), "MyTable")
}