mirror of
https://github.com/ManyakRus/crud_generator.git
synced 2024-12-22 00:36:41 +02:00
999 lines
27 KiB
Go
999 lines
27 KiB
Go
package dbmeta
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/csv"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/ManyakRus/crud_generator/pkg/utils"
|
|
|
|
"go/format"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/jinzhu/inflection"
|
|
"github.com/serenize/snaker"
|
|
"golang.org/x/tools/imports"
|
|
)
|
|
|
|
// GenTemplate template info struct
|
|
type GenTemplate struct {
|
|
Name string
|
|
Content string
|
|
}
|
|
|
|
// TemplateLoader loader function to retrieve a template contents
|
|
type TemplateLoader func(filename string) (tpl *GenTemplate, err error)
|
|
|
|
var replaceFuncMap = template.FuncMap{
|
|
"singular": inflection.Singular,
|
|
"pluralize": inflection.Plural,
|
|
"title": strings.Title,
|
|
"toLower": strings.ToLower,
|
|
"toUpper": strings.ToUpper,
|
|
"toLowerCamelCase": camelToLowerCamel,
|
|
"toUpperCamelCase": camelToUpperCamel,
|
|
"toSnakeCase": snaker.CamelToSnake,
|
|
"StringsJoin": strings.Join,
|
|
"replace": replace,
|
|
"stringifyFirstChar": stringifyFirstChar,
|
|
"FmtFieldName": FmtFieldName,
|
|
}
|
|
|
|
func replace(input, from, to string) string {
|
|
return strings.Replace(input, from, to, -1)
|
|
}
|
|
|
|
// Replace takes a template based name format and will render a name using it
|
|
func Replace(nameFormat, name string) string {
|
|
var tpl bytes.Buffer
|
|
//fmt.Printf("Replace: %s\n",nameFormat)
|
|
t := template.Must(template.New("t1").Funcs(replaceFuncMap).Parse(nameFormat))
|
|
|
|
//sanek start
|
|
if name[0:1] == "$" {
|
|
name = name[1:] + "$"
|
|
}
|
|
//sanek end
|
|
|
|
if err := t.Execute(&tpl, name); err != nil {
|
|
//fmt.Printf("Error creating name format: %s error: %v\n", nameFormat, err)
|
|
return name
|
|
}
|
|
result := tpl.String()
|
|
|
|
result = strings.Trim(result, " \t")
|
|
result = strings.Replace(result, " ", "_", -1)
|
|
result = strings.Replace(result, "\t", "_", -1)
|
|
|
|
//fmt.Printf("Replace( '%s' '%s')= %s\n",nameFormat, name, result)
|
|
return result
|
|
}
|
|
|
|
// ReplaceFileNamingTemplate use the FileNamingTemplate to format a table name
|
|
func (c *Config) ReplaceFileNamingTemplate(name string) string {
|
|
return Replace(c.FileNamingTemplate, name)
|
|
}
|
|
|
|
// ReplaceModelNamingTemplate use the ModelNamingTemplate to format a table name
|
|
func (c *Config) ReplaceModelNamingTemplate(name string) string {
|
|
return Replace(c.ModelNamingTemplate, name)
|
|
}
|
|
|
|
// ReplaceFieldNamingTemplate use the FieldNamingTemplate to format a table name
|
|
func (c *Config) ReplaceFieldNamingTemplate(name string) string {
|
|
return Replace(c.FieldNamingTemplate, name)
|
|
}
|
|
|
|
// GetTemplate return a Template based on a name and template contents
|
|
func (c *Config) GetTemplate(genTemplate *GenTemplate) (*template.Template, error) {
|
|
var s State
|
|
var funcMap = template.FuncMap{
|
|
"ReplaceFileNamingTemplate": c.ReplaceFileNamingTemplate,
|
|
"ReplaceModelNamingTemplate": c.ReplaceModelNamingTemplate,
|
|
"ReplaceFieldNamingTemplate": c.ReplaceFieldNamingTemplate,
|
|
"stringifyFirstChar": stringifyFirstChar,
|
|
"singular": inflection.Singular,
|
|
"pluralize": inflection.Plural,
|
|
"title": strings.Title,
|
|
"toLower": strings.ToLower,
|
|
"toUpper": strings.ToUpper,
|
|
"toLowerCamelCase": camelToLowerCamel,
|
|
"toUpperCamelCase": camelToUpperCamel,
|
|
"FormatSource": FormatSource,
|
|
"toSnakeCase": snaker.CamelToSnake,
|
|
"markdownCodeBlock": markdownCodeBlock,
|
|
"wrapBash": wrapBash,
|
|
"escape": escape,
|
|
"GenerateTableFile": c.GenerateTableFile,
|
|
"GenerateFile": c.GenerateFile,
|
|
"ToJSON": ToJSON,
|
|
"spew": Spew,
|
|
"set": s.Set,
|
|
"inc": s.Inc,
|
|
"StringsJoin": strings.Join,
|
|
"replace": replace,
|
|
"hasField": hasField,
|
|
"FmtFieldName": FmtFieldName,
|
|
"copy": c.FileSystemCopy,
|
|
"mkdir": c.Mkdir,
|
|
"touch": c.Touch,
|
|
"pwd": Pwd,
|
|
"config": c.DisplayConfig,
|
|
"insertFragment": c.insertFragment,
|
|
}
|
|
|
|
baseName := filepath.Base(genTemplate.Name)
|
|
|
|
tmpl, err := template.New(baseName).Option("missingkey=error").Funcs(funcMap).Parse(genTemplate.Content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if baseName == "api.go.tmpl" ||
|
|
baseName == "dao_gorm.go.tmpl" ||
|
|
baseName == "dao_sqlx.go.tmpl" ||
|
|
baseName == "code_dao_sqlx.md.tmpl" ||
|
|
baseName == "code_dao_gorm.md.tmpl" ||
|
|
baseName == "code_http.md.tmpl" {
|
|
|
|
operations := []string{"add", "delete", "get", "getall", "update"}
|
|
for _, op := range operations {
|
|
var filename string
|
|
if baseName == "api.go.tmpl" {
|
|
filename = fmt.Sprintf("api_%s.go.tmpl", op)
|
|
}
|
|
if baseName == "dao_gorm.go.tmpl" {
|
|
filename = fmt.Sprintf("dao_gorm_%s.go.tmpl", op)
|
|
}
|
|
if baseName == "dao_sqlx.go.tmpl" {
|
|
filename = fmt.Sprintf("dao_sqlx_%s.go.tmpl", op)
|
|
}
|
|
|
|
if baseName == "code_dao_sqlx.md.tmpl" {
|
|
filename = fmt.Sprintf("dao_sqlx_%s.go.tmpl", op)
|
|
}
|
|
if baseName == "code_dao_gorm.md.tmpl" {
|
|
filename = fmt.Sprintf("dao_gorm_%s.go.tmpl", op)
|
|
}
|
|
if baseName == "code_http.md.tmpl" {
|
|
filename = fmt.Sprintf("api_%s.go.tmpl", op)
|
|
}
|
|
|
|
var subTemplate *GenTemplate
|
|
if subTemplate, err = c.TemplateLoader(filename); err != nil {
|
|
fmt.Printf("Error loading template %v\n", err)
|
|
return nil, err
|
|
}
|
|
|
|
// fmt.Printf("loading sub template %v\n", filename)
|
|
tmpl.Parse(subTemplate.Content)
|
|
}
|
|
}
|
|
|
|
return tmpl, nil
|
|
}
|
|
|
|
func hasField(v interface{}, name string) bool {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
if rv.Kind() != reflect.Struct {
|
|
return false
|
|
}
|
|
return rv.FieldByName(name).IsValid()
|
|
}
|
|
|
|
// ToJSON func to return json string representation of struct
|
|
func ToJSON(val interface{}, indent int) string {
|
|
pad := fmt.Sprintf("%*s", indent, "")
|
|
strB, _ := json.MarshalIndent(val, "", pad)
|
|
|
|
response := string(strB)
|
|
response = strings.Replace(response, "\n", "", -1)
|
|
return response
|
|
}
|
|
|
|
// Spew func to return spewed string representation of struct
|
|
func Spew(val interface{}) string {
|
|
return spew.Sdump(val)
|
|
}
|
|
|
|
// State struct used for storing state in template parsing
|
|
type State struct {
|
|
n int
|
|
}
|
|
|
|
// Set set state value in template parsing
|
|
func (s *State) Set(n int) int {
|
|
s.n = n
|
|
return n
|
|
}
|
|
|
|
// Inc increment state value in template parsing
|
|
func (s *State) Inc() int {
|
|
s.n++
|
|
return s.n
|
|
}
|
|
|
|
func camelToLowerCamel(s string) string {
|
|
ss := strings.Split(s, "")
|
|
ss[0] = strings.ToLower(ss[0])
|
|
return strings.Join(ss, "")
|
|
}
|
|
|
|
func camelToUpperCamel(s string) string {
|
|
ss := strings.Split(s, "")
|
|
ss[0] = strings.ToUpper(ss[0])
|
|
return strings.Join(ss, "")
|
|
}
|
|
|
|
// FormatSource format source code contents
|
|
func FormatSource(s string) string {
|
|
formattedSource, err := format.Source([]byte(s))
|
|
if err != nil {
|
|
return fmt.Sprintf("Error in formatting source: %s\n", err.Error())
|
|
}
|
|
formattedSource, err = imports.Process("", formattedSource, nil)
|
|
if err != nil {
|
|
return fmt.Sprintf("Error in formatting source: %s\n", err.Error())
|
|
}
|
|
return string(formattedSource)
|
|
}
|
|
|
|
func markdownCodeBlock(contentType, content string) string {
|
|
// fmt.Printf("%s - %s\n", contentType, content)
|
|
return fmt.Sprintf("```%s\n%s\n```\n", contentType, content)
|
|
}
|
|
|
|
func wrapBash(content string) string {
|
|
|
|
r := csv.NewReader(strings.NewReader(content))
|
|
r.Comma = ' '
|
|
record, err := r.Read()
|
|
if err != nil {
|
|
return content
|
|
}
|
|
|
|
fmt.Printf("[%s]\n", content)
|
|
|
|
for i, j := range record {
|
|
fmt.Printf("wrapBash [%d] %s\n", i, j)
|
|
}
|
|
|
|
out := strings.Join(record, " \\\n ")
|
|
return out
|
|
|
|
//
|
|
//
|
|
//r := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
|
|
//arr := r.FindAllString(content, -1)
|
|
//return strings.Join(arr, " \\\n ")
|
|
//
|
|
//
|
|
|
|
//splitter := "[^\\s\"']+|\"[^\"]*\"|'[^']*'"
|
|
//result := RegSplit(content, splitter)
|
|
//return strings.Join(result, " \\\n ")
|
|
|
|
//
|
|
//result, err := parseCommandLine(content)
|
|
//if err != nil {
|
|
// return content
|
|
//}
|
|
//return strings.Join(result, " \\\n ")
|
|
}
|
|
|
|
// RegSplit split text based on regex
|
|
func RegSplit(text string, delimeter string) []string {
|
|
reg := regexp.MustCompile(delimeter)
|
|
indexes := reg.FindAllStringIndex(text, -1)
|
|
laststart := 0
|
|
result := make([]string, len(indexes)+1)
|
|
for i, element := range indexes {
|
|
result[i] = text[laststart:element[0]]
|
|
laststart = element[1]
|
|
}
|
|
result[len(indexes)] = text[laststart:]
|
|
return result
|
|
}
|
|
|
|
func parseCommandLine(command string) ([]string, error) {
|
|
var args []string
|
|
state := "start"
|
|
current := ""
|
|
quote := "\""
|
|
escapeNext := true
|
|
for i := 0; i < len(command); i++ {
|
|
c := command[i]
|
|
|
|
if state == "quotes" {
|
|
if string(c) != quote {
|
|
current += string(c)
|
|
} else {
|
|
args = append(args, current)
|
|
current = ""
|
|
state = "start"
|
|
}
|
|
continue
|
|
}
|
|
|
|
if escapeNext {
|
|
current += string(c)
|
|
escapeNext = false
|
|
continue
|
|
}
|
|
|
|
if c == '\\' {
|
|
escapeNext = true
|
|
continue
|
|
}
|
|
|
|
if c == '"' || c == '\'' {
|
|
state = "quotes"
|
|
quote = string(c)
|
|
continue
|
|
}
|
|
|
|
if state == "arg" {
|
|
if c == ' ' || c == '\t' {
|
|
args = append(args, current)
|
|
current = ""
|
|
state = "start"
|
|
} else {
|
|
current += string(c)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if c != ' ' && c != '\t' {
|
|
state = "arg"
|
|
current += string(c)
|
|
}
|
|
}
|
|
|
|
if state == "quotes" {
|
|
return []string{}, fmt.Errorf("unclosed quote in command line: %s", command)
|
|
}
|
|
|
|
if current != "" {
|
|
args = append(args, current)
|
|
}
|
|
|
|
return args, nil
|
|
}
|
|
|
|
func escape(content string) string {
|
|
content = strings.Replace(content, "\"", "\\\"", -1)
|
|
content = strings.Replace(content, "'", "\\'", -1)
|
|
return content
|
|
}
|
|
|
|
// LoadFragments read all filed inside dirname to `fragments`
|
|
func (c *Config) LoadFragments(dirname string) error {
|
|
c.FragmentsDir = dirname
|
|
files, err := ioutil.ReadDir(dirname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.fragments = &bytes.Buffer{}
|
|
for _, file := range files {
|
|
if !file.IsDir() {
|
|
filename := path.Join(dirname, file.Name())
|
|
content, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
c.fragments.Write(content)
|
|
c.fragments.WriteRune('\n')
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Insert code fragment from file.
|
|
// Within a single file, fragment can be marked as // fragment: <name> ... // end
|
|
func (c *Config) insertFragment(name, defValue string) (string, error) {
|
|
if c.fragments == nil || c.fragments.Len() == 0 {
|
|
return defValue, nil
|
|
}
|
|
// pointer to fragment content
|
|
content := c.fragments.Bytes()
|
|
|
|
// if fragment name is not defined,
|
|
// return whole content
|
|
if name == "" {
|
|
return string(content), nil
|
|
}
|
|
|
|
// Use regex to extract fragment name from given file.
|
|
// Below is an example of code fragment.
|
|
|
|
/*
|
|
//fragment: <name>
|
|
func (w *Model) MyCode() {
|
|
|
|
}
|
|
// end
|
|
*/
|
|
begExp := fmt.Sprintf(`//[\s]{0,}fragment[\s]{0,}:[\s]{0,}%s[\s]{1,}`, name)
|
|
reBeg, err := regexp.Compile(begExp)
|
|
if err != nil {
|
|
return defValue, nil
|
|
}
|
|
begLoc := reBeg.FindIndex(content)
|
|
if begLoc == nil || begLoc[1] == len(content) {
|
|
// not found or no content specified after // fragment: <name>
|
|
return defValue, nil
|
|
}
|
|
|
|
fromIdx := begLoc[1]
|
|
subContent := content[fromIdx:]
|
|
reEnd := regexp.MustCompile(`//[\s]end[\s]{0,}`)
|
|
endLoc := reEnd.FindIndex(subContent)
|
|
if endLoc == nil {
|
|
// not found, return remaining content
|
|
return string(subContent), nil
|
|
}
|
|
return string(subContent[:endLoc[0]]), nil
|
|
}
|
|
|
|
// JSONFieldName convert name to appropriate case
|
|
func (c *Config) JSONFieldName(name string) string {
|
|
return formatFieldName(c.JSONNameFormat, name)
|
|
}
|
|
|
|
// JSONTag converts name to `json:"name"` respecting json-fmt option
|
|
func (c *Config) JSONTag(name string) string {
|
|
return fmt.Sprintf("`json:\"%s\"`", c.JSONFieldName(name))
|
|
}
|
|
|
|
// JSONTagOmitEmpty converts name to JSON tag with omitempty
|
|
func (c *Config) JSONTagOmitEmpty(name string) string {
|
|
return fmt.Sprintf("`json:\"%s,omitempty\"`", c.JSONFieldName(name))
|
|
}
|
|
|
|
// GenerateTableFile generate file from template using specific table used within templates
|
|
func (c *Config) GenerateTableFile(tableName, templateFilename, outputDirectory, outputFileName string) string {
|
|
buf := bytes.Buffer{}
|
|
|
|
buf.WriteString(fmt.Sprintf("GenerateTableFile( %s, %s, %s, %s)\n", tableName, templateFilename, outputDirectory, outputFileName))
|
|
|
|
tableInfo, ok := c.TableInfos[tableName]
|
|
if !ok {
|
|
buf.WriteString(fmt.Sprintf("Table: %s - No tableInfo found\n", tableName))
|
|
return buf.String()
|
|
}
|
|
|
|
if len(tableInfo.Fields) == 0 {
|
|
buf.WriteString(fmt.Sprintf("able: %s - No Fields Available\n", tableName))
|
|
return buf.String()
|
|
}
|
|
|
|
data := c.CreateContextForTableFile(tableInfo)
|
|
|
|
fileOutDir := filepath.Join(c.OutDir, outputDirectory)
|
|
err := os.MkdirAll(fileOutDir, 0777)
|
|
if err != nil && !c.Overwrite {
|
|
buf.WriteString(fmt.Sprintf("unable to create fileOutDir: %s error: %v\n", fileOutDir, err))
|
|
return buf.String()
|
|
}
|
|
|
|
var tpl *GenTemplate
|
|
if tpl, err = c.TemplateLoader(templateFilename); err != nil {
|
|
buf.WriteString(fmt.Sprintf("Error loading template %v\n", err))
|
|
return buf.String()
|
|
}
|
|
|
|
outputFile := filepath.Join(fileOutDir, outputFileName)
|
|
buf.WriteString(fmt.Sprintf("Writing %s -> %s\n", templateFilename, outputFile))
|
|
err = c.WriteTemplate(tpl, data, outputFile)
|
|
return buf.String()
|
|
}
|
|
|
|
// CreateContextForTableFile create map context for a db table
|
|
func (c *Config) CreateContextForTableFile(tableInfo *ModelInfo) map[string]interface{} {
|
|
var modelInfo = map[string]interface{}{
|
|
"StructName": tableInfo.StructName,
|
|
"TableName": tableInfo.DBMeta.TableName(),
|
|
"ShortStructName": strings.ToLower(string(tableInfo.StructName[0])),
|
|
"TableInfo": tableInfo,
|
|
}
|
|
|
|
nonPrimaryKeys := NonPrimaryKeyNames(tableInfo.DBMeta)
|
|
modelInfo["NonPrimaryKeyNamesList"] = nonPrimaryKeys
|
|
modelInfo["NonPrimaryKeysJoined"] = strings.Join(nonPrimaryKeys, ",")
|
|
|
|
primaryKeys := PrimaryKeyNames(tableInfo.DBMeta)
|
|
modelInfo["PrimaryKeyNamesList"] = primaryKeys
|
|
modelInfo["PrimaryKeysJoined"] = strings.Join(primaryKeys, ",")
|
|
|
|
delSQL, err := GenerateDeleteSQL(tableInfo.DBMeta)
|
|
if err == nil {
|
|
modelInfo["delSql"] = delSQL
|
|
}
|
|
|
|
updateSQL, err := GenerateUpdateSQL(tableInfo.DBMeta)
|
|
if err == nil {
|
|
modelInfo["updateSql"] = updateSQL
|
|
}
|
|
|
|
insertSQL, err := GenerateInsertSQL(tableInfo.DBMeta)
|
|
if err == nil {
|
|
modelInfo["insertSql"] = insertSQL
|
|
}
|
|
|
|
selectOneSQL, err := GenerateSelectOneSQL(tableInfo.DBMeta)
|
|
if err == nil {
|
|
modelInfo["selectOneSql"] = selectOneSQL
|
|
}
|
|
|
|
selectMultiSQL, err := GenerateSelectMultiSQL(tableInfo.DBMeta)
|
|
if err == nil {
|
|
modelInfo["selectMultiSql"] = selectMultiSQL
|
|
}
|
|
return modelInfo
|
|
}
|
|
|
|
// WriteTemplate write a template out
|
|
func (c *Config) WriteTemplate(genTemplate *GenTemplate, data map[string]interface{}, outputFile string) error {
|
|
//fmt.Printf("WriteTemplate %s\n", outputFile)
|
|
|
|
if !c.Overwrite && Exists(outputFile) {
|
|
fmt.Printf("not overwriting %s\n", outputFile)
|
|
return nil
|
|
}
|
|
|
|
for key, value := range c.ContextMap {
|
|
data[key] = value
|
|
}
|
|
|
|
dir := filepath.Dir(outputFile)
|
|
parent := filepath.Base(dir)
|
|
|
|
data["File"] = outputFile
|
|
data["Dir"] = dir
|
|
data["Parent"] = parent
|
|
|
|
data["DatabaseName"] = c.SQLDatabase
|
|
data["module"] = c.Module
|
|
|
|
data["modelFQPN"] = c.ModelFQPN
|
|
data["modelPackageName"] = c.ModelPackageName
|
|
|
|
data["daoFQPN"] = c.DaoFQPN
|
|
data["daoPackageName"] = c.DaoPackageName
|
|
|
|
data["UseGuregu"] = c.UseGureguTypes
|
|
|
|
data["apiFQPN"] = c.APIFQPN
|
|
data["apiPackageName"] = c.APIPackageName
|
|
|
|
data["sqlType"] = c.SQLType
|
|
data["sqlConnStr"] = c.SQLConnStr
|
|
data["serverPort"] = c.ServerPort
|
|
data["serverHost"] = c.ServerHost
|
|
data["serverScheme"] = c.ServerScheme
|
|
data["serverListen"] = c.ServerListen
|
|
data["SwaggerInfo"] = c.Swagger
|
|
data["outDir"] = c.OutDir
|
|
data["Config"] = c
|
|
|
|
rt, err := c.GetTemplate(genTemplate)
|
|
if err != nil {
|
|
return fmt.Errorf("error in loading %s template, error: %v", genTemplate.Name, err)
|
|
}
|
|
var buf bytes.Buffer
|
|
err = rt.Execute(&buf, data)
|
|
if err != nil {
|
|
return fmt.Errorf("error in rendering %s: %s", genTemplate.Name, err.Error())
|
|
}
|
|
|
|
fileContents, err := c.format(genTemplate, buf.Bytes(), outputFile)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing %s - error: %v", outputFile, err)
|
|
}
|
|
|
|
err = ioutil.WriteFile(outputFile, fileContents, 0777)
|
|
if err != nil {
|
|
return fmt.Errorf("error writing %s - error: %v", outputFile, err)
|
|
}
|
|
|
|
if c.Verbose {
|
|
fmt.Printf("writing %s\n", outputFile)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) format(genTemplate *GenTemplate, content []byte, outputFile string) ([]byte, error) {
|
|
extension := filepath.Ext(outputFile)
|
|
if extension == ".go" {
|
|
formattedSource, err := format.Source([]byte(content))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error in formatting template: %s outputfile: %s source: %s", genTemplate.Name, outputFile, err.Error())
|
|
}
|
|
|
|
fileContents := NormalizeNewlines(formattedSource)
|
|
if c.LineEndingCRLF {
|
|
fileContents = CRLFNewlines(formattedSource)
|
|
}
|
|
return fileContents, nil
|
|
}
|
|
|
|
fileContents := NormalizeNewlines([]byte(content))
|
|
if c.LineEndingCRLF {
|
|
fileContents = CRLFNewlines(fileContents)
|
|
}
|
|
return fileContents, nil
|
|
}
|
|
|
|
// NormalizeNewlines normalizes \r\n (windows) and \r (mac)
|
|
// into \n (unix)
|
|
func NormalizeNewlines(d []byte) []byte {
|
|
// replace CR LF \r\n (windows) with LF \n (unix)
|
|
d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1)
|
|
// replace CF \r (mac) with LF \n (unix)
|
|
d = bytes.Replace(d, []byte{13}, []byte{10}, -1)
|
|
return d
|
|
}
|
|
|
|
// CRLFNewlines transforms \n to \r\n (windows)
|
|
func CRLFNewlines(d []byte) []byte {
|
|
// replace LF (unix) with CR LF \r\n (windows)
|
|
d = bytes.Replace(d, []byte{10}, []byte{13, 10}, -1)
|
|
return d
|
|
}
|
|
|
|
// Exists reports whether the named file or directory exists.
|
|
func Exists(name string) bool {
|
|
if _, err := os.Stat(name); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GenerateFile generate file from template, non table used within templates
|
|
func (c *Config) GenerateFile(templateFilename, outputDirectory, outputFileName string, overwrite bool) string {
|
|
buf := bytes.Buffer{}
|
|
buf.WriteString(fmt.Sprintf("GenerateFile( %s, %s, %s)\n", templateFilename, outputDirectory, outputFileName))
|
|
fileOutDir := outputDirectory
|
|
err := os.MkdirAll(fileOutDir, 0777)
|
|
if err != nil && !overwrite {
|
|
buf.WriteString(fmt.Sprintf("unable to create fileOutDir: %s error: %v\n", fileOutDir, err))
|
|
return buf.String()
|
|
}
|
|
|
|
data := map[string]interface{}{}
|
|
|
|
var tpl *GenTemplate
|
|
if tpl, err = c.TemplateLoader(templateFilename); err != nil {
|
|
buf.WriteString(fmt.Sprintf("Error loading template %v\n", err))
|
|
return buf.String()
|
|
}
|
|
|
|
outputFile := filepath.Join(fileOutDir, outputFileName)
|
|
buf.WriteString(fmt.Sprintf("Writing %s -> %s\n", templateFilename, outputFile))
|
|
err = c.WriteTemplate(tpl, data, outputFile)
|
|
if err != nil {
|
|
buf.WriteString(fmt.Sprintf("Error calling WriteTemplate %s -> %v\n", templateFilename, err))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// DisplayConfig display config info
|
|
func (c *Config) DisplayConfig() string {
|
|
|
|
info := fmt.Sprintf(
|
|
`DisplayConfig
|
|
SQLType : %s
|
|
SQLConnStr : %s
|
|
SQLDatabase : %s
|
|
Module : %s
|
|
OutDir : %s
|
|
`, c.SQLType, c.SQLConnStr, c.SQLDatabase, c.Module, c.OutDir)
|
|
|
|
return info
|
|
}
|
|
|
|
type copyRules struct {
|
|
result bool
|
|
pattern string
|
|
r *regexp.Regexp
|
|
}
|
|
|
|
// FileSystemCopy template command to copy files, directories and to pass --include XXX and --exclude YYY regular expressions. Files ending in .tmpl will be processed as a template.
|
|
// Files ending in .table.tmpl will be processed as a template iterating through all the tables
|
|
func (c *Config) FileSystemCopy(src, dst string, options ...string) string {
|
|
dstDir := filepath.Join(c.OutDir, dst)
|
|
|
|
patterns := make([]*copyRules, 0)
|
|
|
|
for _, o := range options {
|
|
|
|
if strings.HasPrefix(o, "--exclude ") {
|
|
pattern := o[len("--exclude "):]
|
|
r, _ := regexp.Compile(pattern)
|
|
if r != nil {
|
|
patterns = append(patterns, ©Rules{result: false, r: r, pattern: pattern})
|
|
}
|
|
}
|
|
|
|
if strings.HasPrefix(o, "--include ") {
|
|
pattern := o[len("--include "):]
|
|
r, _ := regexp.Compile(pattern)
|
|
if r != nil {
|
|
patterns = append(patterns, ©Rules{result: true, r: r, pattern: pattern})
|
|
}
|
|
}
|
|
}
|
|
|
|
opt := utils.DefaultCopyOptions()
|
|
|
|
opt.ShouldCopy = func(info os.FileInfo) bool {
|
|
name := info.Name()
|
|
|
|
for _, r := range patterns {
|
|
if r.r.Match([]byte(name)) {
|
|
//fmt.Printf("copy ShouldCopy %s pattern: [%s] result: %t\n", name, r.pattern, r.result)
|
|
return r.result
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
opt.FileHandler = func(src, dest string, info os.FileInfo) utils.FileHandlerFunc {
|
|
|
|
if !opt.ShouldCopy(info) {
|
|
return func(src, dest string, info os.FileInfo, opt utils.Options, results *utils.Results) (err error) {
|
|
results.Info.WriteString(fmt.Sprintf("CopyFile Skipping %s\n", src))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if strings.HasSuffix(src, ".table.tmpl") {
|
|
//fmt.Printf("@@ HandleTableTemplateFile: src: %s dest: %s Name: %s\n", src, dest, info.Name())
|
|
return c.tableFileHandlerFunc
|
|
}
|
|
if strings.HasSuffix(src, ".tmpl") {
|
|
//fmt.Printf("@@ HandleTemplateFile: src: %s dest: %s Name: %s\n", src, dest, info.Name())
|
|
return c.fileHandlerFunc
|
|
}
|
|
|
|
return func(src, dest string, info os.FileInfo, opt utils.Options, results *utils.Results) (err error) {
|
|
results.Info.WriteString(fmt.Sprintf("CopyFile %s\n", dest))
|
|
return utils.DefaultFileCopy(src, dest, info, opt, results)
|
|
}
|
|
}
|
|
|
|
result, err := utils.Copy(src, dstDir, opt)
|
|
if err != nil {
|
|
return fmt.Sprintf("copy returned an error %v", err)
|
|
}
|
|
return fmt.Sprintf("copy %s %s\n%s\n", src, dstDir, result.String())
|
|
}
|
|
|
|
// Mkdir template command to mkdir under the output directory
|
|
func (c *Config) Mkdir(dst string) string {
|
|
dstDir := filepath.Join(c.OutDir, dst)
|
|
|
|
err := os.MkdirAll(dstDir, os.ModePerm)
|
|
if err != nil {
|
|
return fmt.Sprintf("mkdir returned an error %v", err)
|
|
|
|
}
|
|
return fmt.Sprintf("mkdir %s", dstDir)
|
|
}
|
|
|
|
// Touch template command to touch a file under the output directory
|
|
func (c *Config) Touch(dst string) string {
|
|
dstDir := filepath.Join(c.OutDir, dst)
|
|
|
|
_, err := os.Stat(dstDir)
|
|
if os.IsNotExist(err) {
|
|
file, err := os.Create(dstDir)
|
|
if err != nil {
|
|
return fmt.Sprintf("touch returned an error %v", err)
|
|
}
|
|
defer file.Close()
|
|
} else {
|
|
currentTime := time.Now().Local()
|
|
err = os.Chtimes(dstDir, currentTime, currentTime)
|
|
if err != nil {
|
|
return fmt.Sprintf("touch returned an error %v", err)
|
|
}
|
|
}
|
|
return fmt.Sprintf("touch %s", dstDir)
|
|
}
|
|
|
|
// ".tmpl"
|
|
func (c *Config) fileHandlerFunc(src, dest string, info os.FileInfo, opt utils.Options, results *utils.Results) (err error) {
|
|
genTemplate := &GenTemplate{
|
|
Name: info.Name(),
|
|
Content: loadFile(src),
|
|
}
|
|
|
|
data := make(map[string]interface{})
|
|
|
|
outputFile := dest[0 : len(dest)-5]
|
|
results.Info.WriteString(fmt.Sprintf("WriteTemplate %s\n", outputFile))
|
|
return c.WriteTemplate(genTemplate, data, outputFile)
|
|
}
|
|
|
|
// ".table.tmpl"
|
|
func (c *Config) tableFileHandlerFunc(src, dest string, info os.FileInfo, opt utils.Options, results *utils.Results) (err error) {
|
|
genTemplate := &GenTemplate{
|
|
Name: info.Name(),
|
|
Content: loadFile(src),
|
|
}
|
|
|
|
outputFile := dest[0 : len(dest)-11]
|
|
|
|
dir := filepath.Dir(outputFile)
|
|
tmplateName := filepath.Base(outputFile)
|
|
// parent := filepath.Base(dir)
|
|
results.Info.WriteString(fmt.Sprintf("WriteTableTemplate %s\n", src))
|
|
|
|
for tableName, tableInfo := range c.TableInfos {
|
|
data := c.CreateContextForTableFile(tableInfo)
|
|
// fileName := filepath.Join(dir, tableName+name)
|
|
name := c.ReplaceFileNamingTemplate(tableName) + filepath.Ext(tmplateName)
|
|
fileName := filepath.Join(dir, name)
|
|
results.Info.WriteString(fmt.Sprintf(" table: %-25s %s\n", tableName, fileName))
|
|
c.WriteTemplate(genTemplate, data, fileName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func loadFile(src string) string {
|
|
// Read entire file content, giving us little control but
|
|
// making it very simple. No need to close the file.
|
|
content, err := ioutil.ReadFile(src)
|
|
if err != nil {
|
|
return fmt.Sprintf("error loading %s error: %v", src, err)
|
|
}
|
|
|
|
// Convert []byte to string and print to screen
|
|
text := string(content)
|
|
return text
|
|
}
|
|
|
|
// SwaggerInfoDetails swagger details
|
|
type SwaggerInfoDetails struct {
|
|
Version string
|
|
Host string
|
|
BasePath string
|
|
Title string
|
|
Description string
|
|
TOS string
|
|
ContactName string
|
|
ContactURL string
|
|
ContactEmail string
|
|
}
|
|
|
|
// Config for generating code
|
|
type Config struct {
|
|
SQLType string
|
|
SQLConnStr string
|
|
SQLDatabase string
|
|
Module string
|
|
ModelPackageName string
|
|
ModelFQPN string
|
|
AddJSONAnnotation bool
|
|
AddGormAnnotation bool
|
|
AddProtobufAnnotation bool
|
|
AddXMLAnnotation bool
|
|
AddDBAnnotation bool
|
|
UseGureguTypes bool
|
|
JSONNameFormat string
|
|
XMLNameFormat string
|
|
ProtobufNameFormat string
|
|
DaoPackageName string
|
|
DaoFQPN string
|
|
APIPackageName string
|
|
APIFQPN string
|
|
GrpcPackageName string
|
|
GrpcFQPN string
|
|
Swagger *SwaggerInfoDetails
|
|
ServerPort int
|
|
ServerHost string
|
|
ServerScheme string
|
|
ServerListen string
|
|
Verbose bool
|
|
OutDir string
|
|
Overwrite bool
|
|
LineEndingCRLF bool
|
|
CmdLine string
|
|
CmdLineWrapped string
|
|
CmdLineArgs []string
|
|
FileNamingTemplate string
|
|
ModelNamingTemplate string
|
|
FieldNamingTemplate string
|
|
ContextMap map[string]interface{}
|
|
TemplateLoader TemplateLoader
|
|
TableInfos map[string]*ModelInfo
|
|
FragmentsDir string
|
|
fragments *bytes.Buffer
|
|
}
|
|
|
|
// NewConfig create a new code config
|
|
func NewConfig(templateLoader TemplateLoader) *Config {
|
|
conf := &Config{
|
|
Swagger: &SwaggerInfoDetails{
|
|
Version: "1.0",
|
|
BasePath: "/",
|
|
Title: "Swagger Example API",
|
|
Description: "This is a sample server Petstore server.",
|
|
TOS: "",
|
|
ContactName: "",
|
|
ContactURL: "",
|
|
ContactEmail: "",
|
|
},
|
|
TemplateLoader: templateLoader,
|
|
}
|
|
conf.CmdLineArgs = os.Args
|
|
conf.CmdLineWrapped = strings.Join(os.Args, " \\\n ")
|
|
conf.CmdLine = strings.Join(os.Args, " ")
|
|
|
|
conf.ContextMap = make(map[string]interface{})
|
|
|
|
conf.FileNamingTemplate = "{{.}}"
|
|
conf.ModelNamingTemplate = "{{FmtFieldName .}}"
|
|
conf.FieldNamingTemplate = "{{FmtFieldName (stringifyFirstChar .) }}"
|
|
|
|
outDir := "."
|
|
module := "github.com/alexj212/test"
|
|
modelPackageName := "model"
|
|
daoPackageName := "dao"
|
|
apiPackageName := "api"
|
|
|
|
conf.ModelPackageName = modelPackageName
|
|
conf.DaoPackageName = daoPackageName
|
|
conf.APIPackageName = apiPackageName
|
|
|
|
conf.AddJSONAnnotation = true
|
|
conf.AddXMLAnnotation = true
|
|
conf.AddGormAnnotation = true
|
|
conf.AddProtobufAnnotation = true
|
|
conf.AddDBAnnotation = true
|
|
conf.UseGureguTypes = false
|
|
conf.JSONNameFormat = "snake"
|
|
conf.XMLNameFormat = "snake"
|
|
conf.ProtobufNameFormat = "snake"
|
|
conf.Verbose = false
|
|
conf.OutDir = outDir
|
|
conf.Overwrite = true
|
|
|
|
conf.ServerPort = 8080
|
|
conf.ServerHost = "127.0.0.1"
|
|
conf.ServerScheme = "http"
|
|
conf.ServerListen = ":8080"
|
|
conf.Overwrite = true
|
|
|
|
conf.Module = module
|
|
conf.ModelFQPN = module + "/" + modelPackageName
|
|
conf.DaoFQPN = module + "/" + daoPackageName
|
|
conf.APIFQPN = module + "/" + apiPackageName
|
|
|
|
if conf.ServerPort == 80 {
|
|
conf.Swagger.Host = conf.ServerHost
|
|
} else {
|
|
conf.Swagger.Host = fmt.Sprintf("%s:%d", conf.ServerHost, conf.ServerPort)
|
|
}
|
|
|
|
return conf
|
|
}
|