package dbmeta import ( "errors" "fmt" "os" "reflect" "strconv" "strings" "unicode" ) // commonInitialisms is a set of common initialisms. // Only add entries that are highly unlikely to be non-initialisms. // For instance, "ID" is fine (Freudian code is rare), but "AND" is not. var commonInitialisms = map[string]bool{ "API": true, "ASCII": true, "CPU": true, "CSS": true, "DNS": true, "EOF": true, "GUID": true, "HTML": true, "HTTP": true, "HTTPS": true, "ID": true, "IP": true, "JSON": true, "LHS": true, "QPS": true, "RAM": true, "RHS": true, "RPC": true, "SLA": true, "SMTP": true, "SSH": true, "TLS": true, "TTL": true, "UI": true, "UID": true, "UUID": true, "URI": true, "URL": true, "UTF8": true, "VM": true, "XML": true, "ACL": true, } var intToWordMap = []string{ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", } var parsePrimaryKeys = map[string]string{ "uint8": "parseUint8", "uint16": "parseUint16", "uint32": "parseUint32", "uint64": "parseUint64", "int": "parseInt", "int8": "parseInt8", "int16": "parseInt16", "int32": "parseInt32", "int64": "parseInt64", "string": "parseString", "uuid.UUID": "parseUUID", "time.Time": "parseTime", "varbinary": "parseBytes", } var reservedFieldNames = map[string]bool{ "TableName": true, "BeforeSave": true, "Prepare": true, "Validate": true, "type": true, } // RenameReservedName renames a reserved word func RenameReservedName(s string) string { _, match := reservedFieldNames[s] if match { return fmt.Sprintf("%s_", s) } return s } // FmtFieldName formats a string as a struct key // // Example: // // fmtFieldName("foo_id") // // Output: FooID func FmtFieldName(s string) string { name := lintFieldName(s) runes := []rune(name) for i, c := range runes { ok := unicode.IsLetter(c) || unicode.IsDigit(c) if i == 0 { ok = unicode.IsLetter(c) } if !ok { runes[i] = '_' } } fieldName := string(runes) fieldName = RenameReservedName(fieldName) // fmt.Printf("FmtFieldName:%s=%s\n", s, fieldName) return fieldName } func isAllLower(name string) (allLower bool) { allLower = true for _, r := range name { if !unicode.IsLower(r) { allLower = false break } } return } func lintAllLowerFieldName(name string) string { runes := []rune(name) if u := strings.ToUpper(name); commonInitialisms[u] { copy(runes[0:], []rune(u)) } else { runes[0] = unicode.ToUpper(runes[0]) } return string(runes) } func lintFieldName(name string) string { // Fast path for simple cases: "_" and all lowercase. if name == "_" { return name } for len(name) > 0 && name[0] == '_' { name = name[1:] } allLower := isAllLower(name) if allLower { return lintAllLowerFieldName(name) } return lintMixedFieldName(name) } func lintMixedFieldName(name string) string { // Split camelCase at any lower->upper transition, and split on underscores. // Check each word for common initialisms. runes := []rune(name) w, i := 0, 0 // index of start of word, scan for i+1 <= len(runes) { eow := false // whether we hit the end of a word if i+1 == len(runes) { eow = true } else if runes[i+1] == '_' { // underscore; shift the remainder forward over any run of underscores eow = true n := 1 for i+n+1 < len(runes) && runes[i+n+1] == '_' { n++ } // Leave at most one underscore if the underscore is between two digits if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { n-- } copy(runes[i+1:], runes[i+n+1:]) runes = runes[:len(runes)-n] } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { // lower->non-lower eow = true } i++ if !eow { continue } // [w,i) is a word. word := string(runes[w:i]) if u := strings.ToUpper(word); commonInitialisms[u] { // All the common initialisms are ASCII, // so we can replace the bytes exactly. copy(runes[w:], []rune(u)) } else if strings.ToLower(word) == word { // already all lowercase, and not the first word, so uppercase the first character. runes[w] = unicode.ToUpper(runes[w]) } w = i } return string(runes) } // convert first character ints to strings func stringifyFirstChar(str string) string { first := str[:1] i, err := strconv.ParseInt(first, 10, 8) if err != nil { return str } return intToWordMap[i] + "_" + str[1:] } // Copy a src struct into a destination struct func Copy(dst interface{}, src interface{}) error { dstV := reflect.Indirect(reflect.ValueOf(dst)) srcV := reflect.Indirect(reflect.ValueOf(src)) if !dstV.CanAddr() { return errors.New("copy to value is unaddressable") } if srcV.Type() != dstV.Type() { return errors.New("different types can not be copied") } for i := 0; i < dstV.NumField(); i++ { f := srcV.Field(i) if !isZeroOfUnderlyingType(f.Interface()) { dstV.Field(i).Set(f) } } return nil } func isZeroOfUnderlyingType(x interface{}) bool { return x == nil || reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface()) } // Pwd template command to return the current working directory func Pwd() string { currentWorkingDirectory, err := os.Getwd() if err != nil { return fmt.Sprintf("pwd returned an error %v", err) } return currentWorkingDirectory }