322 lines
6.9 KiB
Go
322 lines
6.9 KiB
Go
// Package strmangle is a collection of string manipulation functions.
|
|
// Primarily used by boil and templates for code generation.
|
|
// Because it is focused on pipelining inside templates
|
|
// you will see some odd parameter ordering.
|
|
package strmangle
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/jinzhu/inflection"
|
|
)
|
|
|
|
var (
|
|
idAlphabet = []byte("abcdefghijklmnopqrstuvwxyz")
|
|
uppercaseWords = regexp.MustCompile(`^(?i)(id|uid|uuid|guid|[^aeiouy]*)[0-9]*$`)
|
|
smartQuoteRgx = regexp.MustCompile(`^(?i)"?[a-z_][_a-z0-9]*"?(\."?[_a-z][_a-z0-9]*"?)*(\.\*)?$`)
|
|
)
|
|
|
|
func init() {
|
|
// Override the uncountable inflections with an empty set.
|
|
// This way, people using words like Sheep will not have
|
|
// collisions with their model name (Sheep) and their
|
|
// function name (Sheep()). Instead, it will
|
|
// use the regular inflection rules: Sheep, Sheeps().
|
|
inflection.SetUncountable([]string{})
|
|
}
|
|
|
|
// IdentQuote attempts to quote simple identifiers in SQL tatements
|
|
func IdentQuote(s string) string {
|
|
if strings.ToLower(s) == "null" {
|
|
return s
|
|
}
|
|
|
|
if m := smartQuoteRgx.MatchString(s); m != true {
|
|
return s
|
|
}
|
|
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
splits := strings.Split(s, ".")
|
|
for i, split := range splits {
|
|
if i != 0 {
|
|
buf.WriteByte('.')
|
|
}
|
|
|
|
if strings.HasPrefix(split, `"`) || strings.HasSuffix(split, `"`) || split == "*" {
|
|
buf.WriteString(split)
|
|
continue
|
|
}
|
|
|
|
buf.WriteByte('"')
|
|
buf.WriteString(split)
|
|
buf.WriteByte('"')
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// IdentQuoteSlice applies IdentQuote to a slice.
|
|
func IdentQuoteSlice(s []string) []string {
|
|
if len(s) == 0 {
|
|
return s
|
|
}
|
|
|
|
strs := make([]string, len(s))
|
|
for i, str := range s {
|
|
strs[i] = IdentQuote(str)
|
|
}
|
|
|
|
return strs
|
|
}
|
|
|
|
// Identifier is a base conversion from Base 10 integers to Base 26
|
|
// integers that are represented by an alphabet from a-z
|
|
// See tests for example outputs.
|
|
func Identifier(in int) string {
|
|
ln := len(idAlphabet)
|
|
var n int
|
|
if in == 0 {
|
|
n = 1
|
|
} else {
|
|
n = 1 + int(math.Log(float64(in))/math.Log(float64(ln)))
|
|
}
|
|
|
|
cols := GetBuffer()
|
|
defer PutBuffer(cols)
|
|
|
|
for i := 0; i < n; i++ {
|
|
divisor := int(math.Pow(float64(ln), float64(n-i-1)))
|
|
rem := in / divisor
|
|
cols.WriteByte(idAlphabet[rem])
|
|
|
|
in -= rem * divisor
|
|
}
|
|
|
|
return cols.String()
|
|
}
|
|
|
|
// Plural converts singular words to plural words (eg: person to people)
|
|
func Plural(name string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
splits := strings.Split(name, "_")
|
|
|
|
for i := 0; i < len(splits); i++ {
|
|
if i != 0 {
|
|
buf.WriteByte('_')
|
|
}
|
|
|
|
if i == len(splits)-1 {
|
|
buf.WriteString(inflection.Plural(splits[len(splits)-1]))
|
|
break
|
|
}
|
|
|
|
buf.WriteString(splits[i])
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// Singular converts plural words to singular words (eg: people to person)
|
|
func Singular(name string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
splits := strings.Split(name, "_")
|
|
|
|
for i := 0; i < len(splits); i++ {
|
|
if i != 0 {
|
|
buf.WriteByte('_')
|
|
}
|
|
|
|
if i == len(splits)-1 {
|
|
buf.WriteString(inflection.Singular(splits[len(splits)-1]))
|
|
break
|
|
}
|
|
|
|
buf.WriteString(splits[i])
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// TitleCase changes a snake-case variable name
|
|
// into a go styled object variable name of "ColumnName".
|
|
// titleCase also fully uppercases "ID" components of names, for example
|
|
// "column_name_id" to "ColumnNameID".
|
|
func TitleCase(name string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
splits := strings.Split(name, "_")
|
|
|
|
for _, split := range splits {
|
|
if uppercaseWords.MatchString(split) {
|
|
buf.WriteString(strings.ToUpper(split))
|
|
continue
|
|
}
|
|
|
|
buf.WriteString(strings.Title(split))
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// CamelCase takes a variable name in the format of "var_name" and converts
|
|
// it into a go styled variable name of "varName".
|
|
// camelCase also fully uppercases "ID" components of names, for example
|
|
// "var_name_id" to "varNameID".
|
|
func CamelCase(name string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
splits := strings.Split(name, "_")
|
|
|
|
for i, split := range splits {
|
|
if i == 0 {
|
|
buf.WriteString(split)
|
|
continue
|
|
}
|
|
|
|
if uppercaseWords.MatchString(split) {
|
|
buf.WriteString(strings.ToUpper(split))
|
|
continue
|
|
}
|
|
|
|
buf.WriteString(strings.Title(split))
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// MakeStringMap converts a map[string]string into the format:
|
|
// "key": "value", "key": "value"
|
|
func MakeStringMap(types map[string]string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
c := 0
|
|
for k, v := range types {
|
|
buf.WriteString(fmt.Sprintf(`"%s": "%s"`, k, v))
|
|
if c < len(types)-1 {
|
|
buf.WriteString(", ")
|
|
}
|
|
|
|
c++
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// StringMap maps a function over a slice of strings.
|
|
func StringMap(modifier func(string) string, strs []string) []string {
|
|
ret := make([]string, len(strs))
|
|
|
|
for i, str := range strs {
|
|
ret[i] = modifier(str)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// PrefixStringSlice with the given str.
|
|
func PrefixStringSlice(str string, strs []string) []string {
|
|
ret := make([]string, len(strs))
|
|
|
|
for i, s := range strs {
|
|
ret[i] = fmt.Sprintf("%s%s", str, s)
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// Placeholders generates the SQL statement placeholders for in queries.
|
|
// For example, ($1, $2, $3), ($4, $5, $6) etc.
|
|
// It will start counting placeholders at "start".
|
|
func Placeholders(count int, start int, group int) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
if start == 0 || group == 0 {
|
|
panic("Invalid start or group numbers supplied.")
|
|
}
|
|
|
|
if group > 1 {
|
|
buf.WriteByte('(')
|
|
}
|
|
for i := 0; i < count; i++ {
|
|
if i != 0 {
|
|
if group > 1 && i%group == 0 {
|
|
buf.WriteString("),(")
|
|
} else {
|
|
buf.WriteByte(',')
|
|
}
|
|
}
|
|
buf.WriteString(fmt.Sprintf("$%d", start+i))
|
|
}
|
|
if group > 1 {
|
|
buf.WriteByte(')')
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// SetParamNames takes a slice of columns and returns a comma separated
|
|
// list of parameter names for a template statement SET clause.
|
|
// eg: "col1"=$1, "col2"=$2, "col3"=$3
|
|
func SetParamNames(columns []string) string {
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
for i, c := range columns {
|
|
buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, i+1))
|
|
if i < len(columns)-1 {
|
|
buf.WriteString(", ")
|
|
}
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// WhereClause returns the where clause using start as the $ flag index
|
|
// For example, if start was 2 output would be: "colthing=$2 AND colstuff=$3"
|
|
func WhereClause(start int, cols []string) string {
|
|
if start == 0 {
|
|
panic("0 is not a valid start number for whereClause")
|
|
}
|
|
|
|
buf := GetBuffer()
|
|
defer PutBuffer(buf)
|
|
|
|
for i, c := range cols {
|
|
buf.WriteString(fmt.Sprintf(`"%s"=$%d`, c, start+i))
|
|
if i < len(cols)-1 {
|
|
buf.WriteString(" AND ")
|
|
}
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// JoinSlices merges two string slices of equal length
|
|
func JoinSlices(sep string, a, b []string) []string {
|
|
lna, lnb := len(a), len(b)
|
|
if lna != lnb {
|
|
panic("joinSlices: can only merge slices of same length")
|
|
} else if lna == 0 {
|
|
return nil
|
|
}
|
|
|
|
ret := make([]string, len(a))
|
|
for i, elem := range a {
|
|
ret[i] = fmt.Sprintf("%s%s%s", elem, sep, b[i])
|
|
}
|
|
|
|
return ret
|
|
}
|