diff --git a/dbmeta/codegen.go b/dbmeta/codegen.go index 8a0d5f5..f8a1304 100644 --- a/dbmeta/codegen.go +++ b/dbmeta/codegen.go @@ -5,6 +5,9 @@ import ( "encoding/csv" "encoding/json" "fmt" + "github.com/smallnest/gen/utils" + "time" + "go/format" "io/ioutil" "os" @@ -19,6 +22,7 @@ import ( "github.com/serenize/snaker" ) +// GenTemplate template info struct type GenTemplate struct { Name string Content string @@ -111,10 +115,11 @@ func (c *Config) GetTemplate(genTemplate *GenTemplate) (*template.Template, erro "replace": replace, "hasField": hasField, "FmtFieldName": FmtFieldName, - "copy": FileSystemCopy, - "mkdir": Mkdir, - "touch": Touch, + "copy": c.FileSystemCopy, + "mkdir": c.Mkdir, + "touch": c.Touch, "pwd": Pwd, + "config": c.DisplayConfig, } baseName := filepath.Base(genTemplate.Name) @@ -376,12 +381,12 @@ func (c *Config) JSONTagOmitEmpty(name string) string { } // GenerateTableFile generate file from template using specific table used within templates -func (c *Config) GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, templateFilename, outputDirectory, outputFileName string, formatOutput bool) string { +func (c *Config) GenerateTableFile(tableName, templateFilename, outputDirectory, outputFileName string) string { buf := bytes.Buffer{} - buf.WriteString(fmt.Sprintf("GenerateTableFile( %s, %s, %s, %s, %t)\n", tableName, templateFilename, outputDirectory, outputFileName, formatOutput)) + buf.WriteString(fmt.Sprintf("GenerateTableFile( %s, %s, %s, %)\n", tableName, templateFilename, outputDirectory, outputFileName)) - tableInfo, ok := tableInfos[tableName] + tableInfo, ok := c.TableInfos[tableName] if !ok { buf.WriteString(fmt.Sprintf("Table: %s - No tableInfo found\n", tableName)) return buf.String() @@ -409,7 +414,7 @@ func (c *Config) GenerateTableFile(tableInfos map[string]*ModelInfo, tableName, outputFile := filepath.Join(fileOutDir, outputFileName) buf.WriteString(fmt.Sprintf("Writing %s -> %s\n", templateFilename, outputFile)) - err = c.WriteTemplate(tpl, data, outputFile, formatOutput) + err = c.WriteTemplate(tpl, data, outputFile) return buf.String() } @@ -458,7 +463,9 @@ func (c *Config) CreateContextForTableFile(tableInfo *ModelInfo) map[string]inte } // WriteTemplate write a template out -func (c *Config) WriteTemplate(genTemplate *GenTemplate, data map[string]interface{}, outputFile string, formatOutput bool) error { +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 @@ -468,6 +475,13 @@ func (c *Config) WriteTemplate(genTemplate *GenTemplate, data map[string]interfa 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 @@ -492,43 +506,50 @@ func (c *Config) WriteTemplate(genTemplate *GenTemplate, data map[string]interfa rt, err := c.GetTemplate(genTemplate) if err != nil { - return fmt.Errorf("Error in loading %s template, error: %v\n", genTemplate.Name, err) + 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\n", genTemplate.Name, err.Error()) + 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 formatOutput { - formattedSource, err := format.Source(buf.Bytes()) + 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 fmt.Errorf("Error in formatting template: %s outputfile: %s source: %s\n", genTemplate.Name, outputFile, err.Error()) + 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) } - - err = ioutil.WriteFile(outputFile, fileContents, 0777) - } else { - fileContents := NormalizeNewlines(buf.Bytes()) - if c.LineEndingCRLF { - fileContents = CRLFNewlines(fileContents) - } - - err = ioutil.WriteFile(outputFile, fileContents, 0777) + return fileContents, nil } - if err != nil { - return fmt.Errorf("error writing %s - error: %v\n", outputFile, err) + fileContents := NormalizeNewlines([]byte(content)) + if c.LineEndingCRLF { + fileContents = CRLFNewlines(fileContents) } - - if c.Verbose { - fmt.Printf("writing %s\n", outputFile) - } - return nil + return fileContents, nil } // NormalizeNewlines normalizes \r\n (windows) and \r (mac) @@ -559,7 +580,7 @@ func Exists(name string) bool { } // GenerateFile generate file from template, non table used within templates -func (c *Config) GenerateFile(templateFilename, outputDirectory, outputFileName string, formatOutput bool, overwrite bool) string { +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 @@ -579,13 +600,176 @@ func (c *Config) GenerateFile(templateFilename, outputDirectory, outputFileName outputFile := filepath.Join(fileOutDir, outputFileName) buf.WriteString(fmt.Sprintf("Writing %s -> %s\n", templateFilename, outputFile)) - err = c.WriteTemplate(tpl, data, outputFile, formatOutput) + 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 +} + +// 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) + opt := utils.DefaultCopyOptions() + opt.FileHandler = c.handleFile + + excludePattern := "" + includePattern := "" + + for _, o := range options { + if strings.HasPrefix(o, "--exclude ") { + excludePattern = o[len("--exclude "):] + } + if strings.HasPrefix(o, "--include ") { + includePattern = o[len("--include "):] + } + } + + excludeRegex, _ := regexp.Compile(excludePattern) + if excludeRegex != nil { + fmt.Printf("copy excludePattern: [%s]\n", excludePattern) + } + includeRegex, _ := regexp.Compile(includePattern) + if includeRegex != nil { + fmt.Printf("copy includePattern: [%s]\n", includePattern) + } + + opt.ShouldCopyDir = func(info os.FileInfo) bool { + name := info.Name() + + if includeRegex != nil && includeRegex.Match([]byte(name)) { + return true + } + if excludeRegex != nil && excludeRegex.Match([]byte(name)) { + return false + } + return true + } + + 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) +} + +func (c *Config) handleFile(src, dest string, info os.FileInfo) utils.FileHandlerFunc { + + 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) + } +} + +// ".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) + + 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("WriteTableTemplate %s\n", 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 @@ -637,9 +821,9 @@ type Config struct { FileNamingTemplate string ModelNamingTemplate string FieldNamingTemplate string - string - ContextMap map[string]interface{} - TemplateLoader TemplateLoader + ContextMap map[string]interface{} + TemplateLoader TemplateLoader + TableInfos map[string]*ModelInfo } // NewConfig create a new code config diff --git a/dbmeta/meta.go b/dbmeta/meta.go index 7d8be06..53e6d99 100644 --- a/dbmeta/meta.go +++ b/dbmeta/meta.go @@ -38,7 +38,6 @@ type SQLMappings struct { // SQLMapping mapping type SQLMapping struct { - // SQLType sql type reported from db SQLType string `json:"sql_type"` @@ -554,6 +553,7 @@ func ProcessMappings(source string, mappingJsonstring []byte, verbose bool) erro sqlMappings[value.SQLType] = value } + return nil } @@ -693,13 +693,25 @@ func LoadTableInfo(db *sql.DB, dbTables []string, excludeDbTables []string, conf dbMeta, err := LoadMeta(conf.SQLType, db, conf.SQLDatabase, tableName) if err != nil { - fmt.Printf("Warning - LoadMeta skipping table info for %s error: %v\n", tableName, err) + msg := fmt.Sprintf("Warning - LoadMeta skipping table info for %s error: %v\n", tableName, err) + if au != nil { + fmt.Print(au.Yellow(msg)) + } else { + fmt.Printf(msg) + } + continue } modelInfo, err := GenerateModelInfo(tableInfos, dbMeta, tableName, conf) if err != nil { - fmt.Printf("GenerateModelInfo Error getting table info for %s error: %v\n", tableName, err) + msg := fmt.Sprintf("Error - %v\n", err) + if au != nil { + fmt.Print(au.Red(msg)) + } else { + fmt.Printf(msg) + } + continue } @@ -734,10 +746,11 @@ func GenerateModelInfo(tables map[string]*ModelInfo, dbMeta DbTableMeta, } if conf.Verbose { - fmt.Printf("tableName: %s\n", tableName) + fmt.Printf("\ntableName: %s\n", tableName) for _, c := range dbMeta.Columns() { fmt.Printf(" %s\n", c.String()) } + fmt.Print("\n") } generator := dynamicstruct.NewStruct() diff --git a/dbmeta/meta_utils.go b/dbmeta/meta_utils.go index 4b7ba9b..d342bbe 100644 --- a/dbmeta/meta_utils.go +++ b/dbmeta/meta_utils.go @@ -3,11 +3,20 @@ package dbmeta import ( "database/sql" "fmt" + "github.com/logrusorgru/aurora" "regexp" "strconv" "strings" ) +var ( + au aurora.Aurora +) + +func InitColorOutput(_au aurora.Aurora) { + au = _au +} + // ParseSQLType parse sql type and return raw type and length func ParseSQLType(dbType string) (resultType string, dbTypeLen int64) { @@ -244,7 +253,12 @@ func updateDefaultPrimaryKey(m *dbTableMeta) *dbTableMeta { if !hasPrimary && len(m.columns) > 0 { comments := fmt.Sprintf("Warning table: %s does not have a primary key defined, setting col position 1 %s as primary key\n", m.tableName, m.columns[0].Name()) - fmt.Printf(comments) + if au != nil { + fmt.Print(au.Yellow(comments)) + } else { + fmt.Printf(comments) + } + primaryKeyPos = 0 m.columns[0].isPrimaryKey = true m.columns[0].notes = m.columns[0].notes + comments @@ -252,7 +266,13 @@ func updateDefaultPrimaryKey(m *dbTableMeta) *dbTableMeta { if m.columns[primaryKeyPos].nullable { comments := fmt.Sprintf("Warning table: %s primary key column %s is nullable column, setting it as NOT NULL\n", m.tableName, m.columns[primaryKeyPos].Name()) - fmt.Printf(comments) + + if au != nil { + fmt.Print(au.Yellow(comments)) + } else { + fmt.Printf(comments) + } + m.columns[primaryKeyPos].nullable = false m.columns[0].notes = m.columns[0].notes + comments } diff --git a/dbmeta/util.go b/dbmeta/util.go index 7438234..c1f2af0 100644 --- a/dbmeta/util.go +++ b/dbmeta/util.go @@ -7,10 +7,7 @@ import ( "reflect" "strconv" "strings" - "time" "unicode" - - filecopy "github.com/otiai10/copy" ) // commonInitialisms is a set of common initialisms. @@ -248,41 +245,7 @@ func isZeroOfUnderlyingType(x interface{}) bool { return x == nil || reflect.DeepEqual(x, reflect.Zero(reflect.TypeOf(x)).Interface()) } -func FileSystemCopy(src, dst string) string { - err := filecopy.Copy(src, dst) - if err != nil { - return fmt.Sprintf("copy returned an error %v", err) - - } - return fmt.Sprintf("copy %s %s", src, dst) -} -func Mkdir(dst string) string { - err := os.MkdirAll(dst, os.ModePerm) - if err != nil { - return fmt.Sprintf("mkdir returned an error %v", err) - - } - return fmt.Sprintf("mkdir %s", dst) -} - -func Touch(dst string) string { - _, err := os.Stat(dst) - if os.IsNotExist(err) { - file, err := os.Create(dst) - if err != nil { - return fmt.Sprintf("mkdir returned an error %v", err) - } - defer file.Close() - } else { - currentTime := time.Now().Local() - err = os.Chtimes(dst, currentTime, currentTime) - if err != nil { - return fmt.Sprintf("mkdir returned an error %v", err) - } - } - return fmt.Sprintf("touch %s", dst) -} - +// Pwd template command to return the current working directory func Pwd() string { currentWorkingDirectory, err := os.Getwd() if err != nil { diff --git a/go.mod b/go.mod index 2288dc7..3881f62 100644 --- a/go.mod +++ b/go.mod @@ -8,25 +8,29 @@ require ( github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 github.com/droundy/goopt v0.0.0-20170604162106-0b8effe182da github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 // indirect + github.com/gin-gonic/gin v1.6.3 github.com/gobuffalo/packd v1.0.0 github.com/gobuffalo/packr/v2 v2.8.0 - github.com/gogo/protobuf v1.3.1 // indirect + github.com/gogo/protobuf v1.3.1 github.com/golang/protobuf v1.4.2 // indirect github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 + github.com/guregu/null v4.0.0+incompatible github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 github.com/jimsmart/schema v0.0.4 github.com/jinzhu/gorm v1.9.11 github.com/jinzhu/inflection v1.0.0 + github.com/julienschmidt/httprouter v1.2.0 github.com/karrick/godirwalk v1.15.6 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/lib/pq v1.3.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mattn/go-sqlite3 v2.0.3+incompatible github.com/ompluscator/dynamic-struct v1.2.0 - github.com/otiai10/copy v1.2.0 github.com/rogpeppe/go-internal v1.6.0 // indirect + github.com/satori/go.uuid v1.2.0 github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 - github.com/sirupsen/logrus v1.6.0 // indirect + github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect @@ -36,7 +40,8 @@ require ( golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect golang.org/x/tools v0.0.0-20200730200120-fe6bb45d2132 // indirect google.golang.org/appengine v1.6.5 // indirect + google.golang.org/grpc v1.27.0 google.golang.org/protobuf v1.24.0 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v2 v2.3.0 honnef.co/go/tools v0.0.1-2020.1.4 // indirect ) diff --git a/main.go b/main.go index a1e14d8..c46b4c3 100644 --- a/main.go +++ b/main.go @@ -126,11 +126,10 @@ func listTemplates() { } } -func loadContextMapping(conf *dbmeta.Config) { +func loadContextMapping(conf *dbmeta.Config) error { contextFile, err := os.Open(*contextFileName) if err != nil { - fmt.Printf("Error loading context file %s error: %v\n", *contextFileName, err) - return + return err } defer contextFile.Close() @@ -138,14 +137,14 @@ func loadContextMapping(conf *dbmeta.Config) { err = jsonParser.Decode(&conf.ContextMap) if err != nil { - fmt.Printf("Error loading context file %s error: %v\n", *contextFileName, err) - return + return err } fmt.Printf("Loaded Context from %s with %d defaults\n", *contextFileName, len(conf.ContextMap)) for key, value := range conf.ContextMap { - fmt.Printf(" Context:%s -> %s\n", key, value) + fmt.Printf(" Context:%s -> %v\n", key, value) } + return nil } func main() { @@ -153,13 +152,10 @@ func main() { // fmt.Printf("[%2d] %s\n", i, arg) //} au = aurora.NewAurora(!*noColorOutput) + dbmeta.InitColorOutput(au) baseTemplates = packr.New("gen", "./template") - if *verbose { - listTemplates() - } - if *saveTemplateDir != "" { saveTemplates() return @@ -261,7 +257,12 @@ func main() { } if *contextFileName != "" { - loadContextMapping(conf) + err = loadContextMapping(conf) + if err != nil { + fmt.Print(au.Red(fmt.Sprintf("Error loading context file %s error: %v\n", *contextFileName, err))) + os.Exit(1) + return + } } tableInfos = dbmeta.LoadTableInfo(db, dbTables, excludeDbTables, conf) @@ -278,6 +279,7 @@ func main() { i++ } + conf.TableInfos = tableInfos conf.ContextMap["tableInfos"] = tableInfos if *execCustomScript != "" { @@ -290,6 +292,10 @@ func main() { return } + if *verbose { + listTemplates() + } + err = generate(conf) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error in executing generate %v\n", err))) @@ -576,7 +582,7 @@ func generate(conf *dbmeta.Config) error { modelInfo := conf.CreateContextForTableFile(tableInfo) modelFile := filepath.Join(modelDir, CreateGoSrcFileName(tableName)) - err = conf.WriteTemplate(ModelTmpl, modelInfo, modelFile, true) + err = conf.WriteTemplate(ModelTmpl, modelInfo, modelFile) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -584,7 +590,7 @@ func generate(conf *dbmeta.Config) error { if *restAPIGenerate { restFile := filepath.Join(apiDir, CreateGoSrcFileName(tableName)) - err = conf.WriteTemplate(ControllerTmpl, modelInfo, restFile, true) + err = conf.WriteTemplate(ControllerTmpl, modelInfo, restFile) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -595,7 +601,7 @@ func generate(conf *dbmeta.Config) error { if *daoGenerate { //write dao outputFile := filepath.Join(daoDir, CreateGoSrcFileName(tableName)) - err = conf.WriteTemplate(DaoTmpl, modelInfo, outputFile, true) + err = conf.WriteTemplate(DaoTmpl, modelInfo, outputFile) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -612,21 +618,21 @@ func generate(conf *dbmeta.Config) error { } if *daoGenerate { - err = conf.WriteTemplate(DaoInitTmpl, data, filepath.Join(daoDir, "dao_base.go"), true) + err = conf.WriteTemplate(DaoInitTmpl, data, filepath.Join(daoDir, "dao_base.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) } } - err = conf.WriteTemplate(ModelBaseTmpl, data, filepath.Join(modelDir, "model_base.go"), true) + err = conf.WriteTemplate(ModelBaseTmpl, data, filepath.Join(modelDir, "model_base.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) } if *modGenerate { - err = conf.WriteTemplate(GoModuleTmpl, data, filepath.Join(*outDir, "go.mod"), false) + err = conf.WriteTemplate(GoModuleTmpl, data, filepath.Join(*outDir, "go.mod")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -691,13 +697,13 @@ func generateRestBaseFiles(conf *dbmeta.Config, apiDir string) (err error) { return } - err = conf.WriteTemplate(RouterTmpl, data, filepath.Join(apiDir, "router.go"), true) + err = conf.WriteTemplate(RouterTmpl, data, filepath.Join(apiDir, "router.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) } - err = conf.WriteTemplate(HTTPUtilsTmpl, data, filepath.Join(apiDir, "http_utils.go"), true) + err = conf.WriteTemplate(HTTPUtilsTmpl, data, filepath.Join(apiDir, "http_utils.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -724,7 +730,7 @@ func generateMakefile(conf *dbmeta.Config) (err error) { populateProtoCinContext(conf, data) } - err = conf.WriteTemplate(MakefileTmpl, data, filepath.Join(*outDir, "Makefile"), false) + err = conf.WriteTemplate(MakefileTmpl, data, filepath.Join(*outDir, "Makefile")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -751,7 +757,7 @@ func generateProtobufDefinitionFile(conf *dbmeta.Config, data map[string]interfa } protofile := fmt.Sprintf("%s.proto", *sqlDatabase) - err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(*outDir, protofile), false) + err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(*outDir, protofile)) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -772,7 +778,7 @@ func generateProtobufDefinitionFile(conf *dbmeta.Config, data map[string]interfa return err } - err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(serverDir, "main.go"), true) + err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(serverDir, "main.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -783,7 +789,7 @@ func generateProtobufDefinitionFile(conf *dbmeta.Config, data map[string]interfa return err } - err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(serverDir, "protoserver.go"), true) + err = conf.WriteTemplate(ProtobufTmpl, data, filepath.Join(serverDir, "protoserver.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -885,13 +891,13 @@ func generateProjectFiles(conf *dbmeta.Config, data map[string]interface{}) (err return } populateProtoCinContext(conf, data) - err = conf.WriteTemplate(GitIgnoreTmpl, data, filepath.Join(*outDir, ".gitignore"), false) + err = conf.WriteTemplate(GitIgnoreTmpl, data, filepath.Join(*outDir, ".gitignore")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) } - err = conf.WriteTemplate(ReadMeTmpl, data, filepath.Join(*outDir, "README.md"), false) + err = conf.WriteTemplate(ReadMeTmpl, data, filepath.Join(*outDir, "README.md")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) @@ -934,7 +940,7 @@ func generateServerCode(conf *dbmeta.Config) (err error) { fmt.Print(au.Red(fmt.Sprintf("unable to create serverDir: %s error: %v\n", serverDir, err))) return } - err = conf.WriteTemplate(MainServerTmpl, data, filepath.Join(serverDir, "main.go"), true) + err = conf.WriteTemplate(MainServerTmpl, data, filepath.Join(serverDir, "main.go")) if err != nil { fmt.Print(au.Red(fmt.Sprintf("Error writing file: %v\n", err))) os.Exit(1) diff --git a/utils/copy.go b/utils/copy.go new file mode 100644 index 0000000..ade9bb9 --- /dev/null +++ b/utils/copy.go @@ -0,0 +1,202 @@ +package utils + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" +) + +const ( + // tmpPermissionForDirectory makes the destination directory writable, + // so that stuff can be copied recursively even if any original directory is NOT writable. + // See https://github.com/otiai10/copy/pull/9 for more information. + tmpPermissionForDirectory = os.FileMode(0755) +) + +// Copy copies src to dest, doesn't matter if src is a directory or a file. +func Copy(src, dest string, opt ...Options) (*Results, error) { + results := &Results{} + info, err := os.Lstat(src) + if err != nil { + return results, err + } + + err = switchboard(src, dest, info, assure(opt...), results) + return results, err +} + +// switchboard switches proper copy functions regarding file type, etc... +// If there would be anything else here, add a case to this switchboard. +func switchboard(src, dest string, info os.FileInfo, opt Options, results *Results) error { + switch { + case info.Mode()&os.ModeSymlink != 0: + return onsymlink(src, dest, info, opt, results) + case info.IsDir(): + return dcopy(src, dest, info, opt, results) + default: + return fcopy(src, dest, info, opt, results) + } +} + +// copy decide if this src should be copied or not. +// Because this "copy" could be called recursively, +// "info" MUST be given here, NOT nil. +func copy(src, dest string, info os.FileInfo, opt Options, results *Results) error { + skip, err := opt.Skip(src) + if err != nil { + return err + } + if skip { + return nil + } + return switchboard(src, dest, info, opt, results) +} + +// fcopy is for just a file, +// with considering existence of parent directory +// and file permission. +func fcopy(src, dest string, info os.FileInfo, opt Options, results *Results) (err error) { + + if err = os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil { + return + } + + handler := DefaultFileCopy + if opt.FileHandler != nil { + handler = opt.FileHandler(src, dest, info) + } + + return handler(src, dest, info, opt, results) +} + +// DefaultFileCopy file copy that can be called from external +func DefaultFileCopy(src, dest string, info os.FileInfo, opt Options, results *Results) (err error) { + f, err := os.Create(dest) + if err != nil { + return + } + defer fclose(f, &err) + + if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil { + return + } + + s, err := os.Open(src) + if err != nil { + return + } + defer fclose(s, &err) + + if _, err = io.Copy(f, s); err != nil { + return + } + + if opt.Sync { + err = f.Sync() + } + results.FilesCopied++ + + return +} + +// dcopy is for a directory, +// with scanning contents inside the directory +// and pass everything to "copy" recursively. +func dcopy(srcdir, destdir string, info os.FileInfo, opt Options, results *Results) (err error) { + if opt.ShouldCopyDir != nil && !opt.ShouldCopyDir(info) { + return nil + } + + originalMode := info.Mode() + // Make dest dir with 0755 so that everything writable. + if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil { + return + } + results.DirsCopied++ + // Recover dir mode with original one. + defer chmod(destdir, originalMode|opt.AddPermission, &err) + + contents, err := ioutil.ReadDir(srcdir) + if err != nil { + return + } + + for _, content := range contents { + cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name()) + + if err = copy(cs, cd, content, opt, results); err != nil { + // If any error, exit immediately + return + } + } + + return +} + +func onsymlink(src, dest string, info os.FileInfo, opt Options, results *Results) error { + + switch opt.OnSymlink(src) { + case Shallow: + return lcopy(src, dest, results) + case Deep: + orig, err := os.Readlink(src) + if err != nil { + return err + } + info, err = os.Lstat(orig) + if err != nil { + return err + } + return copy(orig, dest, info, opt, results) + case Skip: + fallthrough + default: + return nil // do nothing + } +} + +// lcopy is for a symlink, +// with just creating a new symlink by replicating src symlink. +func lcopy(src, dest string, results *Results) error { + src, err := os.Readlink(src) + if err != nil { + return err + } + results.SymLinksCreated++ + return os.Symlink(src, dest) +} + +// fclose ANYHOW closes file, +// with asiging error raised during Close, +// BUT respecting the error already reported. +func fclose(f *os.File, reported *error) { + if err := f.Close(); *reported == nil { + *reported = err + } +} + +// chmod ANYHOW changes file mode, +// with asiging error raised during Chmod, +// BUT respecting the error already reported. +func chmod(dir string, mode os.FileMode, reported *error) { + if err := os.Chmod(dir, mode); *reported == nil { + *reported = err + } +} + +// assure Options struct, should be called only once. +// All optional values MUST NOT BE nil/zero after assured. +func assure(opts ...Options) Options { + if len(opts) == 0 { + return DefaultCopyOptions() + } + defopt := DefaultCopyOptions() + if opts[0].OnSymlink == nil { + opts[0].OnSymlink = defopt.OnSymlink + } + if opts[0].Skip == nil { + opts[0].Skip = defopt.Skip + } + return opts[0] +} diff --git a/utils/options.go b/utils/options.go new file mode 100644 index 0000000..6d9d3ef --- /dev/null +++ b/utils/options.go @@ -0,0 +1,55 @@ +package utils + +import "os" + +// FileHandlerFunc type to define a function for carrying out a file processing for copying or processing as a template +type FileHandlerFunc func(src, dest string, info os.FileInfo, opt Options, results *Results) (err error) + +// Options specifies optional actions on copying. +type Options struct { + // OnSymlink can specify what to do on symlink + OnSymlink func(src string) SymlinkAction + // Skip can specify which files should be skipped + Skip func(src string) (bool, error) + // AddPermission to every entities, + // NO MORE THAN 0777 + AddPermission os.FileMode + // Sync file after copy. + // Useful in case when file must be on the disk + // (in case crash happens, for example), + // at the expense of some performance penalty + Sync bool + + // FileHandler - returns a handler for file - if nill will use the default copy handler + FileHandler func(src, dest string, info os.FileInfo) FileHandlerFunc + + // ShouldCopyDir - return bool if dir should be copied + ShouldCopyDir func(opt os.FileInfo) bool +} + +// SymlinkAction represents what to do on symlink. +type SymlinkAction int + +const ( + // Deep creates hard-copy of contents. + Deep SymlinkAction = iota + // Shallow creates new symlink to the dest of symlink. + Shallow + // Skip does nothing with symlink. + Skip +) + +// DefaultCopyOptions provides default options, +// which would be modified by usage-side. +func DefaultCopyOptions() Options { + return Options{ + OnSymlink: func(string) SymlinkAction { + return Shallow // Do shallow copy + }, + Skip: func(string) (bool, error) { + return false, nil // Don't skip + }, + AddPermission: 0, // Add nothing + Sync: false, // Do not sync + } +} diff --git a/utils/results.go b/utils/results.go new file mode 100644 index 0000000..3a07e2e --- /dev/null +++ b/utils/results.go @@ -0,0 +1,25 @@ +package utils + +import ( + "bytes" + "fmt" +) + +// Results specifies results of the copy. +type Results struct { + FilesCopied int + DirsCopied int + SymLinksCreated int + + Info bytes.Buffer +} + +func (c *Results) String() string { + return fmt.Sprintf(`Results + FilesCopied : %d + DirsCopied : %d + SymLinksCreated : %d + +%s +`, c.FilesCopied, c.DirsCopied, c.SymLinksCreated, string(c.Info.Bytes())) +}