From 74d443367a7854756f400abd5ad0ec836bbc712b Mon Sep 17 00:00:00 2001 From: Alex Jeannopoulos Date: Mon, 3 Aug 2020 18:26:34 -0700 Subject: [PATCH 1/2] updated exec handling, error reporting --- dbmeta/codegen.go | 252 +++++++++++++++++++++++++++++++++++++------ dbmeta/meta.go | 21 +++- dbmeta/meta_utils.go | 24 ++++- dbmeta/util.go | 39 +------ go.mod | 13 ++- main.go | 58 +++++----- utils/copy.go | 202 ++++++++++++++++++++++++++++++++++ utils/options.go | 55 ++++++++++ utils/results.go | 25 +++++ 9 files changed, 581 insertions(+), 108 deletions(-) create mode 100644 utils/copy.go create mode 100644 utils/options.go create mode 100644 utils/results.go 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())) +} From 2574c97d0e1434df028cf8b0d747a9b257917748 Mon Sep 17 00:00:00 2001 From: Alex Jeannopoulos Date: Mon, 3 Aug 2020 22:03:05 -0700 Subject: [PATCH 2/2] updated exec - copy command for include and exclude patterns --- README.md | 22 +++++++-- _test/dbmeta/main.go | 2 +- code_http.md | 4 +- dbmeta/codegen.go | 91 +++++++++++++++++++++---------------- main.go | 2 +- packrd/packed-packr.go | 2 +- readme/main.go | 4 +- release.history | 4 ++ template/GEN_README.md.tmpl | 7 +++ utils/copy.go | 4 +- utils/options.go | 4 +- 11 files changed, 94 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 1f8b86e..db21d5d 100644 --- a/README.md +++ b/README.md @@ -294,10 +294,17 @@ Loop thru map of tables, key is the table name and value is ModelInfo. Creating ``` ### Example - copy a file or directory from source to a target directory. +copy function updated to provide --include and --exclude patterns. Patterns are processed in order, an include preceeding an exclude will take precedence. Multiple include and excludes can be specified. Files ending with .table.tmpl will be processed for each table. Output filenames will be stored in the proper directory, with a name of the table with the suffix of the template extension. Files ending with .tmpl will be processed as a template and the filename will be the name of the template stripped with the .tmpl suffix. + ``` {{ copy "../_test" "test" }} +{{ copy "./backend" "test/backend" "--exclude .idea|commands" "--exclude go.sum" "--include .*" }} + +{{ copy "./backend" "test/backend" "--include backend" "--include go.mod" "--exclude .*" }} + + ``` @@ -332,18 +339,21 @@ The following info is available within use of the exec template. "AdvancesSample" string "\n{{ range $i, $table := .tables }}\n {{$singular := singular $table -}}\n {{$plural := pluralize $table -}}\n {{$title := title $table -}}\n {{$lower := toLower $table -}}\n {{$lowerCamel := toLowerCamelCase $table -}}\n {{$snakeCase := toSnakeCase $table -}}\n {{ printf \"[%-2d] %-20s %-20s %-20s %-20s %-20s %-20s %-20s\" $i $table $singular $plural $title $lower $lowerCamel $snakeCase}}{{- end }}\n\n\n{{ range $i, $table := .tables }}\n {{$name := toUpper $table -}}\n {{$filename := printf \"My%s\" $name -}}\n {{ printf \"[%-2d] %-20s %-20s\" $i $table $filename}}\n {{ GenerateTableFile $table \"custom.go.tmpl\" \"test\" $filename true}}\n{{- end }}\n" - "Config" *dbmeta.Config &dbmeta.Config{SQLType:"sqlite3", SQLConnStr:"./example/sample.db", SQLDatabase:"main", Module:"github.com/alexj212/test", ModelPackageName:"model", ModelFQPN:"github.com/alexj212/test/model", AddJSONAnnotation:true, AddGormAnnotation:true, AddProtobufAnnotation:true, AddXMLAnnotation:true, AddDBAnnotation:true, UseGureguTypes:false, JSONNameFormat:"snake", XMLNameFormat:"snake", ProtobufNameFormat:"", DaoPackageName:"dao", DaoFQPN:"github.com/alexj212/test/dao", APIPackageName:"api", APIFQPN:"github.com/alexj212/test/api", GrpcPackageName:"", GrpcFQPN:"", Swagger:(*dbmeta.SwaggerInfoDetails)(0xc000470480), ServerPort:8080, ServerHost:"127.0.0.1", ServerScheme:"http", ServerListen:":8080", Verbose:false, OutDir:".", Overwrite:true, LineEndingCRLF:false, CmdLine:"/tmp/go-build665746825/b001/exe/readme --sqltype=sqlite3 --connstr ./example/sample.db --database main --table invoices", CmdLineWrapped:"/tmp/go-build665746825/b001/exe/readme \\\n --sqltype=sqlite3 \\\n --connstr \\\n ./example/sample.db \\\n --database \\\n main \\\n --table \\\n invoices", CmdLineArgs:[]string{"/tmp/go-build665746825/b001/exe/readme", "--sqltype=sqlite3", "--connstr", "./example/sample.db", "--database", "main", "--table", "invoices"}, FileNamingTemplate:"{{.}}", ModelNamingTemplate:"{{FmtFieldName .}}", FieldNamingTemplate:"{{FmtFieldName (stringifyFirstChar .) }}", string:"", ContextMap:map[string]interface {}{"GenHelp":"Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n", "tableInfos":map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc00057b400)}}, TemplateLoader:(dbmeta.TemplateLoader)(0x89ff10)} + "Config" *dbmeta.Config &dbmeta.Config{SQLType:"sqlite3", SQLConnStr:"./example/sample.db", SQLDatabase:"main", Module:"github.com/alexj212/test", ModelPackageName:"model", ModelFQPN:"github.com/alexj212/test/model", AddJSONAnnotation:true, AddGormAnnotation:true, AddProtobufAnnotation:true, AddXMLAnnotation:true, AddDBAnnotation:true, UseGureguTypes:false, JSONNameFormat:"snake", XMLNameFormat:"snake", ProtobufNameFormat:"", DaoPackageName:"dao", DaoFQPN:"github.com/alexj212/test/dao", APIPackageName:"api", APIFQPN:"github.com/alexj212/test/api", GrpcPackageName:"", GrpcFQPN:"", Swagger:(*dbmeta.SwaggerInfoDetails)(0xc000ad0510), ServerPort:8080, ServerHost:"127.0.0.1", ServerScheme:"http", ServerListen:":8080", Verbose:false, OutDir:".", Overwrite:true, LineEndingCRLF:false, CmdLine:"/tmp/go-build271698611/b001/exe/readme --sqltype=sqlite3 --connstr ./example/sample.db --database main --table invoices", CmdLineWrapped:"/tmp/go-build271698611/b001/exe/readme \\\n --sqltype=sqlite3 \\\n --connstr \\\n ./example/sample.db \\\n --database \\\n main \\\n --table \\\n invoices", CmdLineArgs:[]string{"/tmp/go-build271698611/b001/exe/readme", "--sqltype=sqlite3", "--connstr", "./example/sample.db", "--database", "main", "--table", "invoices"}, FileNamingTemplate:"{{.}}", ModelNamingTemplate:"{{FmtFieldName .}}", FieldNamingTemplate:"{{FmtFieldName (stringifyFirstChar .) }}", ContextMap:map[string]interface {}{"GenHelp":"Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n", "tableInfos":map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc0001e94a0)}}, TemplateLoader:(dbmeta.TemplateLoader)(0x8a7e40), TableInfos:map[string]*dbmeta.ModelInfo(nil)} "DatabaseName" string "main" + "Dir" string "." + "File" string "./README.md" "GenHelp" string "Usage of gen:\n\tgen [-v] --sqltype=mysql --connstr \"user:password@/dbname\" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj]\ngit fetch up\n sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n\n\nOptions:\n --sqltype=mysql sql database type such as [ mysql, mssql, postgres, sqlite, etc. ]\n -c, --connstr=nil database connection string\n -d, --database=nil Database to for connection\n -t, --table= Table to build struct from\n -x, --exclude= Table(s) to exclude\n --templateDir= Template Dir\n --save= Save templates to dir\n --model=model name to set for model package\n --model_naming={{FmtFieldName .}} model naming template to name structs\n --field_naming={{FmtFieldName (stringifyFirstChar .) }} field naming template to name structs\n --file_naming={{.}} file_naming template to name files\n --dao=dao name to set for dao package\n --api=api name to set for api package\n --grpc=grpc name to set for grpc package\n --out=. output dir\n --module=example.com/example module path\n --overwrite Overwrite existing files (default)\n --no-overwrite disable overwriting files\n --windows use windows line endings in generated files\n --no-color disable color output\n --context= context file (json) to populate context with\n --mapping= mapping file (json) to map sql types to golang/protobuf etc\n --exec= execute script for custom code generation\n --json Add json annotations (default)\n --no-json Disable json annotations\n --json-fmt=snake json name format [snake | camel | lower_camel | none]\n --xml Add xml annotations (default)\n --no-xml Disable xml annotations\n --xml-fmt=snake xml name format [snake | camel | lower_camel | none]\n --gorm Add gorm annotations (tags)\n --protobuf Add protobuf annotations (tags)\n --proto-fmt=snake proto name format [snake | camel | lower_camel | none]\n --gogo-proto= location of gogo import \n --db Add db annotations (tags)\n --guregu Add guregu null types\n --copy-templates Copy regeneration templates to project directory\n --mod Generate go.mod in output dir\n --makefile Generate Makefile in output dir\n --server Generate server app output dir\n --generate-dao Generate dao functions\n --generate-proj Generate project readme and gitignore\n --rest Enable generating RESTful api\n --run-gofmt run gofmt on output dir\n --listen= listen address e.g. :8080\n --scheme=http scheme for server url\n --host=localhost host for server\n --port=8080 port for server\n --swagger_version=1.0 swagger version\n --swagger_path=/ swagger base path\n --swagger_tos= swagger tos url\n --swagger_contact_name=Me swagger contact name\n --swagger_contact_url=http://me.com/terms.html swagger contact url\n --swagger_contact_email=me@me.com swagger contact email\n -v, --verbose Enable verbose output\n --name_test= perform name test using the --model_naming or --file_naming options\n -h, --help Show usage message\n --version Show version\n\n" "NonPrimaryKeyNamesList" []string []string{"CustomerId", "InvoiceDate", "BillingAddress", "BillingCity", "BillingState", "BillingCountry", "BillingPostalCode", "Total"} "NonPrimaryKeysJoined" string "CustomerId,InvoiceDate,BillingAddress,BillingCity,BillingState,BillingCountry,BillingPostalCode,Total" + "Parent" string "." "PrimaryKeyNamesList" []string []string{"InvoiceId"} "PrimaryKeysJoined" string "InvoiceId" - "ReleaseHistory" string "- v0.9.26 (07/31/2020)\n - Release scripting\n - Added custom script functions to copy, mkdir, touch, pwd\n - Fixed custom script exec example\n- v0.9.25 (07/26/2020)\n - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format\n - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file.\n - Added --scheme and --listen options. This allows compiled binary to be used behind reverse proxy.\n - In addition, template for generating URL was fixed, i.e. when PORT is 80, then PORT is omitted from URL segment.\n- v0.9.24 (07/13/2020)\n - Fixed array bounds issue parsing mysql db meta\n- v0.9.23 (07/10/2020)\n - Added postgres types: bigserial, serial, smallserial, bigserial, float4 to mapping.json\n- v0.9.22 (07/08/2020)\n - Modified gogo.proto check to use GOPATH not hardcoded.\n - Updated gen to error exit on first error encountered\n - Added color output for error\n - Added --no-color option for non colorized output\n- v0.9.21 (07/07/2020)\n - Repacking templates, update version number in info.\n- v0.9.20 (07/07/2020)\n - Fixed render error in router.go.tmpl\n - upgraded project to use go.mod 1.14\n- v0.9.19 (07/07/2020)\n - Added --windows flag to write files with CRLF windows line endings, otherwise they are all unix based LF line endings\n- v0.9.18 (06/30/2020)\n - Fixed naming in templates away from hard coded model package.\n- v0.9.17 (06/30/2020)\n - Refactored template loading, to better report error in template\n - Added option to run gofmt on output directory\n- v0.9.16 (06/29/2020)\n - Fixes to router.go.tmpl from calvinchengx\n - Added postgres db support for inet and timestamptz\n- v0.9.15 (06/23/2020)\n - Code cleanup using gofmt name suggestions.\n - Template updates for generated code cleanup using gofmt name suggestions.\n- v0.9.14 (06/23/2020)\n - Added model comment on field line if available from database.\n - Added exposing TableInfo via api call.\n- v0.9.13 (06/22/2020)\n - fixed closing of connections via defer\n - bug fixes in sqlx generated code\n- v0.9.12 (06/14/2020)\n - SQLX changed MustExec to Exec and checking/returning error\n - Updated field renaming if duplicated, need more elegant renaming solution.\n - Added exclude to test.sh\n- v0.9.11 (06/13/2020)\n - Added ability to pass field, model and file naming format\n - updated test scripts\n - Fixed sqlx sql query placeholders\n- v0.9.10 (06/11/2020)\n - Bug fix with retrieving varchar length from mysql\n - Added support for mysql unsigned decimal - maps to float\n- v0.9.9 (06/11/2020)\n - Fixed issue with mysql and table named `order`\n - Fixed internals in GetAll generation in gorm and sqlx.\n- v0.9.8 (06/10/2020)\n - Added ability to set file naming convention for models, dao, apis and grpc `--file_naming={{.}}`\n - Added ability to set struct naming convention `--model_naming={{.}}`\n - Fixed bug with Makefile generation removing quoted conn string in `make regen`\n- v0.9.7 (06/09/2020)\n - Added grpc server generation - WIP (looking for code improvements)\n - Added ability to exclude tables\n - Added support for unsigned from mysql ddl.\n- v0.9.6 (06/08/2020)\n - Updated SQLX codegen\n - Updated templates to split code gen functions into seperate files\n - Added code_dao_gorm, code_dao_sqlx to be generated from templates\n- v0.9.5 (05/16/2020)\n - Added SQLX codegen by default, split dao templates.\n - Renamed templates\n- v0.9.4 (05/15/2020)\n - Documentation updates, samples etc.\n- v0.9.3 (05/14/2020)\n - Template bug fixes, when using custom api, dao and model package.\n - Set primary key if not set to the first column\n - Skip code gen if primary key column is not int or string\n - validated codegen for mysql, mssql, postgres and sqlite3\n - Fixed file naming if table ends with _test.go renames to _tst.go\n - Fix for duplicate field names in struct due to renaming\n - Added Notes for columns and tables for situations where a primary key is set since not defined in db\n - Fixed issue when model contained field that had were named the same as funcs within model.\n- v0.9.2 (05/12/2020)\n - Code cleanup gofmt, etc.\n- v0.9.1 (05/12/2020)\n- v0.9 (05/12/2020)\n - updated db meta data loading fetching default values\n - added default value to GORM tags\n - Added protobuf .proto generation\n - Added test app to display meta data\n - Cleanup DDL generation\n - Added support for varchar2, datetime2, float8, USER_DEFINED\n- v0.5\n" + "ReleaseHistory" string "- v0.9.27 (08/04/2020)\n - Updated '--exec' mode to provide various functions for processing\n - copy function updated to provide --include and --exclude patterns. Patterns are processed in order, an include preceeding an exclude will take precedence. Multiple include and excludes can be specified. Files ending with .table.tmpl will be processed for each table. Output filenames will be stored in the proper directory, with a name of the table with the suffix of the template extension. Files ending with .tmpl will be processed as a template and the filename will be the name of the template stripped with the .tmpl suffix. \n - When processing templates, files generated with a .go extension will be formatted with the go fmt.\n- v0.9.26 (07/31/2020)\n - Release scripting\n - Added custom script functions to copy, mkdir, touch, pwd\n - Fixed custom script exec example\n- v0.9.25 (07/26/2020)\n - Adhere json-fmt flag for all JSON response so when camel or lower_camel is specified, fields name in GetAll variant and DDL info will also have the same name format\n - Fix: Build information embedded through linker in Makefile is not consistent with the variable defined in main file.\n - Added --scheme and --listen options. This allows compiled binary to be used behind reverse proxy.\n - In addition, template for generating URL was fixed, i.e. when PORT is 80, then PORT is omitted from URL segment.\n- v0.9.24 (07/13/2020)\n - Fixed array bounds issue parsing mysql db meta\n- v0.9.23 (07/10/2020)\n - Added postgres types: bigserial, serial, smallserial, bigserial, float4 to mapping.json\n- v0.9.22 (07/08/2020)\n - Modified gogo.proto check to use GOPATH not hardcoded.\n - Updated gen to error exit on first error encountered\n - Added color output for error\n - Added --no-color option for non colorized output\n- v0.9.21 (07/07/2020)\n - Repacking templates, update version number in info.\n- v0.9.20 (07/07/2020)\n - Fixed render error in router.go.tmpl\n - upgraded project to use go.mod 1.14\n- v0.9.19 (07/07/2020)\n - Added --windows flag to write files with CRLF windows line endings, otherwise they are all unix based LF line endings\n- v0.9.18 (06/30/2020)\n - Fixed naming in templates away from hard coded model package.\n- v0.9.17 (06/30/2020)\n - Refactored template loading, to better report error in template\n - Added option to run gofmt on output directory\n- v0.9.16 (06/29/2020)\n - Fixes to router.go.tmpl from calvinchengx\n - Added postgres db support for inet and timestamptz\n- v0.9.15 (06/23/2020)\n - Code cleanup using gofmt name suggestions.\n - Template updates for generated code cleanup using gofmt name suggestions.\n- v0.9.14 (06/23/2020)\n - Added model comment on field line if available from database.\n - Added exposing TableInfo via api call.\n- v0.9.13 (06/22/2020)\n - fixed closing of connections via defer\n - bug fixes in sqlx generated code\n- v0.9.12 (06/14/2020)\n - SQLX changed MustExec to Exec and checking/returning error\n - Updated field renaming if duplicated, need more elegant renaming solution.\n - Added exclude to test.sh\n- v0.9.11 (06/13/2020)\n - Added ability to pass field, model and file naming format\n - updated test scripts\n - Fixed sqlx sql query placeholders\n- v0.9.10 (06/11/2020)\n - Bug fix with retrieving varchar length from mysql\n - Added support for mysql unsigned decimal - maps to float\n- v0.9.9 (06/11/2020)\n - Fixed issue with mysql and table named `order`\n - Fixed internals in GetAll generation in gorm and sqlx.\n- v0.9.8 (06/10/2020)\n - Added ability to set file naming convention for models, dao, apis and grpc `--file_naming={{.}}`\n - Added ability to set struct naming convention `--model_naming={{.}}`\n - Fixed bug with Makefile generation removing quoted conn string in `make regen`\n- v0.9.7 (06/09/2020)\n - Added grpc server generation - WIP (looking for code improvements)\n - Added ability to exclude tables\n - Added support for unsigned from mysql ddl.\n- v0.9.6 (06/08/2020)\n - Updated SQLX codegen\n - Updated templates to split code gen functions into seperate files\n - Added code_dao_gorm, code_dao_sqlx to be generated from templates\n- v0.9.5 (05/16/2020)\n - Added SQLX codegen by default, split dao templates.\n - Renamed templates\n- v0.9.4 (05/15/2020)\n - Documentation updates, samples etc.\n- v0.9.3 (05/14/2020)\n - Template bug fixes, when using custom api, dao and model package.\n - Set primary key if not set to the first column\n - Skip code gen if primary key column is not int or string\n - validated codegen for mysql, mssql, postgres and sqlite3\n - Fixed file naming if table ends with _test.go renames to _tst.go\n - Fix for duplicate field names in struct due to renaming\n - Added Notes for columns and tables for situations where a primary key is set since not defined in db\n - Fixed issue when model contained field that had were named the same as funcs within model.\n- v0.9.2 (05/12/2020)\n - Code cleanup gofmt, etc.\n- v0.9.1 (05/12/2020)\n- v0.9 (05/12/2020)\n - updated db meta data loading fetching default values\n - added default value to GORM tags\n - Added protobuf .proto generation\n - Added test app to display meta data\n - Cleanup DDL generation\n - Added support for varchar2, datetime2, float8, USER_DEFINED\n- v0.5\n" "ShortStructName" string "i" "StructName" string "Invoices" "SwaggerInfo" *dbmeta.SwaggerInfoDetails &dbmeta.SwaggerInfoDetails{Version:"1.0.0", Host:"127.0.0.1:8080", BasePath:"/", Title:"Sample CRUD api for main db", Description:"Sample CRUD api for main db", TOS:"My Custom TOS", ContactName:"", ContactURL:"", ContactEmail:""} - "TableInfo" *dbmeta.ModelInfo &dbmeta.ModelInfo{Index:0, IndexPlus1:1, PackageName:"model", StructName:"Invoices", ShortStructName:"i", TableName:"invoices", Fields:[]string{"//[ 0] InvoiceId integer null: false primary: true isArray: false auto: true col: integer len: -1 default: []\n InvoiceID int32 `gorm:\"primary_key;AUTO_INCREMENT;column:InvoiceId;type:integer;\" json:\"invoice_id\" xml:\"invoice_id\" db:\"InvoiceId\" protobuf:\"int32,0,opt,name=InvoiceId\"`", "//[ 1] CustomerId integer null: false primary: false isArray: false auto: false col: integer len: -1 default: []\n CustomerID int32 `gorm:\"column:CustomerId;type:integer;\" json:\"customer_id\" xml:\"customer_id\" db:\"CustomerId\" protobuf:\"int32,1,opt,name=CustomerId\"`", "//[ 2] InvoiceDate datetime null: false primary: false isArray: false auto: false col: datetime len: -1 default: []\n InvoiceDate time.Time `gorm:\"column:InvoiceDate;type:datetime;\" json:\"invoice_date\" xml:\"invoice_date\" db:\"InvoiceDate\" protobuf:\"google.protobuf.Timestamp,2,opt,name=InvoiceDate\"`", "//[ 3] BillingAddress nvarchar(70) null: true primary: false isArray: false auto: false col: nvarchar len: 70 default: []\n BillingAddress sql.NullString `gorm:\"column:BillingAddress;type:nvarchar;size:70;\" json:\"billing_address\" xml:\"billing_address\" db:\"BillingAddress\" protobuf:\"string,3,opt,name=BillingAddress\"`", "//[ 4] BillingCity nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCity sql.NullString `gorm:\"column:BillingCity;type:nvarchar;size:40;\" json:\"billing_city\" xml:\"billing_city\" db:\"BillingCity\" protobuf:\"string,4,opt,name=BillingCity\"`", "//[ 5] BillingState nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingState sql.NullString `gorm:\"column:BillingState;type:nvarchar;size:40;\" json:\"billing_state\" xml:\"billing_state\" db:\"BillingState\" protobuf:\"string,5,opt,name=BillingState\"`", "//[ 6] BillingCountry nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCountry sql.NullString `gorm:\"column:BillingCountry;type:nvarchar;size:40;\" json:\"billing_country\" xml:\"billing_country\" db:\"BillingCountry\" protobuf:\"string,6,opt,name=BillingCountry\"`", "//[ 7] BillingPostalCode nvarchar(10) null: true primary: false isArray: false auto: false col: nvarchar len: 10 default: []\n BillingPostalCode sql.NullString `gorm:\"column:BillingPostalCode;type:nvarchar;size:10;\" json:\"billing_postal_code\" xml:\"billing_postal_code\" db:\"BillingPostalCode\" protobuf:\"string,7,opt,name=BillingPostalCode\"`", "//[ 8] Total numeric null: false primary: false isArray: false auto: false col: numeric len: -1 default: []\n Total float64 `gorm:\"column:Total;type:numeric;\" json:\"total\" xml:\"total\" db:\"Total\" protobuf:\"float,8,opt,name=Total\"`"}, DBMeta:(*dbmeta.dbTableMeta)(0xc00009b2c0), Instance:(*struct { BillingState string "json:\"billing_state\""; BillingCountry string "json:\"billing_country\""; BillingPostalCode string "json:\"billing_postal_code\""; Total float64 "json:\"total\""; CustomerID int "json:\"customer_id\""; InvoiceDate time.Time "json:\"invoice_date\""; BillingAddress string "json:\"billing_address\""; BillingCity string "json:\"billing_city\""; InvoiceID int "json:\"invoice_id\"" })(0xc0005f7e00), CodeFields:[]*dbmeta.FieldInfo{(*dbmeta.FieldInfo)(0xc0005c6640), (*dbmeta.FieldInfo)(0xc0005c6780), (*dbmeta.FieldInfo)(0xc0005c68c0), (*dbmeta.FieldInfo)(0xc0005c6a00), (*dbmeta.FieldInfo)(0xc0005c6b40), (*dbmeta.FieldInfo)(0xc0005c6c80), (*dbmeta.FieldInfo)(0xc0005c6dc0), (*dbmeta.FieldInfo)(0xc0005c6f00), (*dbmeta.FieldInfo)(0xc0005c7040)}} + "TableInfo" *dbmeta.ModelInfo &dbmeta.ModelInfo{Index:0, IndexPlus1:1, PackageName:"model", StructName:"Invoices", ShortStructName:"i", TableName:"invoices", Fields:[]string{"//[ 0] InvoiceId integer null: false primary: true isArray: false auto: true col: integer len: -1 default: []\n InvoiceID int32 `gorm:\"primary_key;AUTO_INCREMENT;column:InvoiceId;type:integer;\" json:\"invoice_id\" xml:\"invoice_id\" db:\"InvoiceId\" protobuf:\"int32,0,opt,name=InvoiceId\"`", "//[ 1] CustomerId integer null: false primary: false isArray: false auto: false col: integer len: -1 default: []\n CustomerID int32 `gorm:\"column:CustomerId;type:integer;\" json:\"customer_id\" xml:\"customer_id\" db:\"CustomerId\" protobuf:\"int32,1,opt,name=CustomerId\"`", "//[ 2] InvoiceDate datetime null: false primary: false isArray: false auto: false col: datetime len: -1 default: []\n InvoiceDate time.Time `gorm:\"column:InvoiceDate;type:datetime;\" json:\"invoice_date\" xml:\"invoice_date\" db:\"InvoiceDate\" protobuf:\"google.protobuf.Timestamp,2,opt,name=InvoiceDate\"`", "//[ 3] BillingAddress nvarchar(70) null: true primary: false isArray: false auto: false col: nvarchar len: 70 default: []\n BillingAddress sql.NullString `gorm:\"column:BillingAddress;type:nvarchar;size:70;\" json:\"billing_address\" xml:\"billing_address\" db:\"BillingAddress\" protobuf:\"string,3,opt,name=BillingAddress\"`", "//[ 4] BillingCity nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCity sql.NullString `gorm:\"column:BillingCity;type:nvarchar;size:40;\" json:\"billing_city\" xml:\"billing_city\" db:\"BillingCity\" protobuf:\"string,4,opt,name=BillingCity\"`", "//[ 5] BillingState nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingState sql.NullString `gorm:\"column:BillingState;type:nvarchar;size:40;\" json:\"billing_state\" xml:\"billing_state\" db:\"BillingState\" protobuf:\"string,5,opt,name=BillingState\"`", "//[ 6] BillingCountry nvarchar(40) null: true primary: false isArray: false auto: false col: nvarchar len: 40 default: []\n BillingCountry sql.NullString `gorm:\"column:BillingCountry;type:nvarchar;size:40;\" json:\"billing_country\" xml:\"billing_country\" db:\"BillingCountry\" protobuf:\"string,6,opt,name=BillingCountry\"`", "//[ 7] BillingPostalCode nvarchar(10) null: true primary: false isArray: false auto: false col: nvarchar len: 10 default: []\n BillingPostalCode sql.NullString `gorm:\"column:BillingPostalCode;type:nvarchar;size:10;\" json:\"billing_postal_code\" xml:\"billing_postal_code\" db:\"BillingPostalCode\" protobuf:\"string,7,opt,name=BillingPostalCode\"`", "//[ 8] Total numeric null: false primary: false isArray: false auto: false col: numeric len: -1 default: []\n Total float64 `gorm:\"column:Total;type:numeric;\" json:\"total\" xml:\"total\" db:\"Total\" protobuf:\"float,8,opt,name=Total\"`"}, DBMeta:(*dbmeta.dbTableMeta)(0xc000133b00), Instance:(*struct { BillingState string "json:\"billing_state\""; BillingCountry string "json:\"billing_country\""; BillingPostalCode string "json:\"billing_postal_code\""; CustomerID int "json:\"customer_id\""; InvoiceDate time.Time "json:\"invoice_date\""; BillingAddress string "json:\"billing_address\""; BillingCity string "json:\"billing_city\""; Total float64 "json:\"total\""; InvoiceID int "json:\"invoice_id\"" })(0xc000a85700), CodeFields:[]*dbmeta.FieldInfo{(*dbmeta.FieldInfo)(0xc00025c640), (*dbmeta.FieldInfo)(0xc00025c780), (*dbmeta.FieldInfo)(0xc00025c8c0), (*dbmeta.FieldInfo)(0xc00025ca00), (*dbmeta.FieldInfo)(0xc00025cb40), (*dbmeta.FieldInfo)(0xc00025cc80), (*dbmeta.FieldInfo)(0xc00025cdc0), (*dbmeta.FieldInfo)(0xc00025cf00), (*dbmeta.FieldInfo)(0xc00025d040)}} "TableName" string "invoices" "apiFQPN" string "github.com/alexj212/test/api" "apiPackageName" string "api" @@ -363,7 +373,7 @@ The following info is available within use of the exec template. "serverScheme" string "http" "sqlConnStr" string "./example/sample.db" "sqlType" string "sqlite3" - "tableInfos" map[string]*dbmeta.ModelInfo map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc00057b400)} + "tableInfos" map[string]*dbmeta.ModelInfo map[string]*dbmeta.ModelInfo{"invoices":(*dbmeta.ModelInfo)(0xc0001e94a0)} "updateSql" string "UPDATE `invoices` set CustomerId = ?, InvoiceDate = ?, BillingAddress = ?, BillingCity = ?, BillingState = ?, BillingCountry = ?, BillingPostalCode = ?, Total = ? WHERE InvoiceId = ?" @@ -419,6 +429,10 @@ Table Name: registration_source |ms sql |y | y | y | y | y | y| n ## Version History +- v0.9.27 (08/04/2020) + - Updated '--exec' mode to provide various functions for processing + - copy function updated to provide --include and --exclude patterns. Patterns are processed in order, an include preceeding an exclude will take precedence. Multiple include and excludes can be specified. Files ending with .table.tmpl will be processed for each table. Output filenames will be stored in the proper directory, with a name of the table with the suffix of the template extension. Files ending with .tmpl will be processed as a template and the filename will be the name of the template stripped with the .tmpl suffix. + - When processing templates, files generated with a .go extension will be formatted with the go fmt. - v0.9.26 (07/31/2020) - Release scripting - Added custom script functions to copy, mkdir, touch, pwd diff --git a/_test/dbmeta/main.go b/_test/dbmeta/main.go index d5cf069..2adc78d 100644 --- a/_test/dbmeta/main.go +++ b/_test/dbmeta/main.go @@ -30,7 +30,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful meta data viewer for SQl databases" } - goopt.Version = "v0.9.26 (07/31/2020)" + goopt.Version = "v0.9.27 (08/04/2020)" goopt.Summary = `dbmeta [-v] --sqltype=mysql --connstr "user:password@/dbname" --database sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] diff --git a/code_http.md b/code_http.md index d337a6e..6567211 100644 --- a/code_http.md +++ b/code_http.md @@ -138,7 +138,7 @@ func GetInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // @Failure 400 {object} api.HTTPError // @Failure 404 {object} api.HTTPError // @Router /invoices [post] -// echo '{"billing_state": "USFzllBypZZJDjtbuDWBnWcrU","billing_country": "KgrvVzwkLcFjnchqPwVoHWkRT","billing_postal_code": "ScJIRKwUTRltasmLeQNMLxJYS","total": 0.03391519763659607,"customer_id": 66,"invoice_date": "2123-11-18T21:39:12.15674111-08:00","billing_address": "nPoieNSaqVDVMBQvfwSXcYdtM","billing_city": "ObTQBQeAscRjOcAkHOOKmkHXU","invoice_id": 7}' | http POST "http://127.0.0.1:8080/invoices" X-Api-User:user123 +// echo '{"billing_state": "ZDIUXoQexYEYgpaYJgyitZpjS","billing_country": "xbuWQjVGOCsrKSXCJhxeVSFhQ","billing_postal_code": "fEHjgTFVSOwVZEPNUjpVkmOZV","customer_id": 40,"invoice_date": "2021-12-17T01:29:16.603984343-08:00","billing_address": "GoPOJOqToKgHAXoCRwOLzSsgW","billing_city": "pRhdTdgdcpgiBrgvmCMlFKapm","total": 0.38248355155004915,"invoice_id": 46}' | http POST "http://127.0.0.1:8080/invoices" X-Api-User:user123 func AddInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ctx := initializeContext(r) invoices := &model.Invoices{} @@ -192,7 +192,7 @@ func AddInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // @Failure 400 {object} api.HTTPError // @Failure 404 {object} api.HTTPError // @Router /invoices/{argInvoiceID} [put] -// echo '{"billing_state": "USFzllBypZZJDjtbuDWBnWcrU","billing_country": "KgrvVzwkLcFjnchqPwVoHWkRT","billing_postal_code": "ScJIRKwUTRltasmLeQNMLxJYS","total": 0.03391519763659607,"customer_id": 66,"invoice_date": "2123-11-18T21:39:12.15674111-08:00","billing_address": "nPoieNSaqVDVMBQvfwSXcYdtM","billing_city": "ObTQBQeAscRjOcAkHOOKmkHXU","invoice_id": 7}' | http PUT "http://127.0.0.1:8080/invoices/1" X-Api-User:user123 +// echo '{"billing_state": "ZDIUXoQexYEYgpaYJgyitZpjS","billing_country": "xbuWQjVGOCsrKSXCJhxeVSFhQ","billing_postal_code": "fEHjgTFVSOwVZEPNUjpVkmOZV","customer_id": 40,"invoice_date": "2021-12-17T01:29:16.603984343-08:00","billing_address": "GoPOJOqToKgHAXoCRwOLzSsgW","billing_city": "pRhdTdgdcpgiBrgvmCMlFKapm","total": 0.38248355155004915,"invoice_id": 46}' | http PUT "http://127.0.0.1:8080/invoices/1" X-Api-User:user123 func UpdateInvoices(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { ctx := initializeContext(r) diff --git a/dbmeta/codegen.go b/dbmeta/codegen.go index f8a1304..2ebef9a 100644 --- a/dbmeta/codegen.go +++ b/dbmeta/codegen.go @@ -622,46 +622,77 @@ func (c *Config) DisplayConfig() string { 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) - opt := utils.DefaultCopyOptions() - opt.FileHandler = c.handleFile - excludePattern := "" - includePattern := "" + patterns := make([]*copyRules, 0) for _, o := range options { + if strings.HasPrefix(o, "--exclude ") { - excludePattern = o[len("--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 ") { - includePattern = o[len("--include "):] + pattern := o[len("--include "):] + r, _ := regexp.Compile(pattern) + if r != nil { + patterns = append(patterns, ©Rules{result: true, r: r, pattern: pattern}) + } } } - 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 := utils.DefaultCopyOptions() - opt.ShouldCopyDir = func(info os.FileInfo) bool { + opt.ShouldCopy = 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 + 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) @@ -702,23 +733,6 @@ func (c *Config) Touch(dst string) string { 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{ @@ -745,13 +759,14 @@ func (c *Config) tableFileHandlerFunc(src, dest string, info os.FileInfo, opt ut 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("WriteTableTemplate %s\n", fileName)) + results.Info.WriteString(fmt.Sprintf(" table: %-25s %s\n", tableName, fileName)) c.WriteTemplate(genTemplate, data, fileName) } return nil diff --git a/main.go b/main.go index c46b4c3..269db6b 100644 --- a/main.go +++ b/main.go @@ -101,7 +101,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful API generator for SQl databases" } - goopt.Version = "v0.9.26 (07/31/2020)" + goopt.Version = "v0.9.27 (08/04/2020)" goopt.Summary = `gen [-v] --sqltype=mysql --connstr "user:password@/dbname" --database --module=example.com/example [--json] [--gorm] [--guregu] [--generate-dao] [--generate-proj] git fetch up sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] diff --git a/packrd/packed-packr.go b/packrd/packed-packr.go index 66f60a8..1493994 100644 --- a/packrd/packed-packr.go +++ b/packrd/packed-packr.go @@ -33,7 +33,7 @@ var _ = func() error { "93bd183343e73a133e7c243e834a15f8": "1f8b08000000000000ffac554d73db36103d13bf62c31399b854cf4d7d882d67c69d54f2d4b1a7d31b082c243820402f405baaa3ffde014849946ccff4201f2c72f1f6bdfd20765b2e7ef005c2cb4b25b9bbe9df66bcc1cd8631ddb48e02142ccb85b301572167598e448e7c7c524d32102a8322e48c65f94287655757c23593076dff5d769385a32667256393095c74dac86bab1c680f9d4709c18144a52d425822f0b6355af0a09d853a62415be5ce80dbf8f48022c013371d7ad0363878d23cb9f5d0969c40ef2b16d62d8ea47ca04e0478610c00601bc594070419fff940da2ec029785ee256f7997b689194a30625286d0cc610a05ec32f7f83704dab0d82327cc1b23d1fc0c0c6583699c0371ed0874bd7343a9c48eb8072ac958298754d8d74cab406c65752616eaf6f4e28d4f3c19b42737f5aa1b93f16faabb34137787fb2da8d08774a1bd67f99dfdce2f6d180eaac284458c170b5aacbfef70cfca3197c4ac69e3841b1fd72af88662e7c759d9590ae611f1fa17024c1ba002a9eb16c0c8473504da8ae225e15f9009eb900e93c2ffb1a5c11dd595e1bfceefee4e497dcfc713b9f8d651ebcb3d0f2b5715c8270445d1b5264efb81e09bfe51ec5f7b9ddb5b1fa5fb98e951d0977c90e8a6be3536e07c02319596ff1896194deb5f548e135bd4ef611fd01f035fd803fa69fa2c1b7a297c93ea23f00bea61ff0c7f4175cde70e28d1f73d75c42db5b5bee7dfa2093c61e7d2430f2d8290c1d985e00a142422b300d661e78cd3db26c7a01fbbf8f71a257d38b3eb22f6dbb1fb507eefb01ceb203d4c7dde33026dd6281942e449afc61c9033c6b63a046d0f6c9fd4009352a4708b842d1857839fda361d9e0da5fa9b864d29ab974ed1a387812dbe19fd60507893e68dbef97fe8445d5e450489f70488a0b7cd99c25ff91a11c0affc232e9c33dfc760ec3e6abaeadd48422145bc37ddc52731539cb92659ec4ffc37b12656cb956f0218a5497dc7e91928a32ca6684a123dbc7e1ab193e17b988b9c65d1819d25ab55c4a42efe385cc4b966d7abe1843f57ddd6251c2877348ecc3eb3bd452abd4ce00716e7910dcc68e08d76a945b66e50874ccedd7cfa0e1f79e77d6355f351a59949f417ffa94045404a520fa235db22ce5a9fd3f486eaeeeac44326b6d17292c555d6f6b5f947d8ca9ee3bf7ea1643a122cba60f6548c16a13676ddfd977c857878dad9d3351616058c1f979a4819f3f772d9b22b6578f1d37c5ea6c678cdcbb1e46e2b92a5665791039dbb0ff020000ffff099f892465090000", "9ec72c1db17b72aeed9a9dfae4794622": "1f8b08000000000000ff84924d6bdb401086effb2b5e924b0c967a2f6d21d8b49796b46e028510a2b176b45ebada51b52bbb69c87f2f2bc91f710cbe8e9e79e69dd1de9752d7ece3c37b7cf884abdb950db00104c39e5b8aac5159c7681c5360b0b61141bab664588ffc5de4ba7114394cd491eada39d4a26d654b8a563c36d6392c194e429ce2493aac68cd58327b6ca8f5acdf38264a5d5e62b6b89b637e7d83cf9d2f932aa8c2b02f06e336283409aa2d015b21ae1845966d814c931469b98642608d28e835396e578c5234a3249f12965d8852db7facb1b171b51569928fbda37264527be0d87ff35433641898523454fe26c3b952b303ebfea2d6236e2469d6d2b5618a9f3fbefec2f2099a2bea5c04798d2f378b6ffbf1fdc8b48cb475a194da451ead56fcf4e81efb5b54d2aa0cf70b8eade5b5f5062d97d2ea30f81b32d69b87abcb11e0ec3b19d6d962802607bd0c4268b84c7f75941cf60d95be61d6720a417b6aa81c32778d3e6286ca213367c7af99a1b263d20bd9a5eb83630cae8aa230a29e9fb78f14179ae431fc717f1f0d47722e3792c7ba7117c85f5e12feda368c38a339ed18d73f6f20ad4f1bc6e39c37743d785a325eefbc44f7e05bc9ff000000ffff6236e43020040000", "9ecd3f03cbe3e3ef5337a260a193f876": "1f8b08000000000000ff8c945f6fd33014c59f934f71895448509ac1847898d4876d5d61b07f6a2721046873939b60e1d8ad7dc356ac7c77e4c4ddbaf247ed4b1dfbdedf39b64f626d8125970851c1d48d598afb9b0a890991552aa37a21a2b60df7f6e01dd2a110d66633d24d4e17acc6b6056e8041d9c89cb892400a2a24606004cf1154091a73a58bd824506a5583b5d9359b0bf4dde4c6c025d077746b63466ccecc7ab9f08f4e7ec134ab0d0c61c12a04f7f3438dcb060d61017181256b0419e7e355f24797e1bf1086209b7a8efad19c710698e73e61ec6f41942e50f7d2c51c8cd2e4a772259a5aba5ad45a6918c289d6178a26aa9145ea8a275c16fd62e84eebefa719e7740fb99284f7941df7ff69672c7ddc0097f4f64dea850d692eab04628da673fde5db4b6bb35a1528ae58fe8355fe2cb32da9144811135375e7b64fa9f3d6fb4bc08681590a381841646d6650604ee78d203e5b8ab68dc230e0a5d77f368228720d6eaa37633ae78c4b73285771579642f4e26b1475e420e83b5d6318046d18b49bc0d103f0a1ccdaec4af39ae9d5475c990f8a4b2c9c8d75e3f8281b6bfe13b5db599c7488da98a5e8396e2b23286bca660bcd25957134305e6dbe828181cbc96476720d8302a6979f663039b93e7e0f93d3e9ec71eef2e2ec739482590a7ff2dbd79284410b280cc23f1c2d94a14aa3d9c9d4edc0dc6ed83a3b3d3f75831d1dec2ab0c6a6bba0438f1c1f65539c7359c4662992fe06ce5455f561905c74f2fd8ccb73c74dfacb7219eb08b32e533ee17dd5739fe0753d2fbb486e303552a3a57b4c61f8ba4b6c8fcd25411fe083917bb1a6eaee5835d283a3a7df9ce87fec070fc3fd0dfef662eedf97b00d436b51166d1bfe0e0000ffff9fb98df146050000", - "a2d5586edb9ff79f53fe610c4e91de3d": "1f8b08000000000000ffc45b7b6f1cb991ff7bfb5354465e443266ba6d2f2e08b4510259921f881e8e242fb2d01a1e4e774d0f576cb245b225cf8e749ffd5045f663f470bcb85c6e01db43b25855ac2a56fd48f65ee4a6aa50fb4fdbf097bfc2e6f9423a900e0494a8d10a8f05cca542a8150a878085f4e04c637304a921cd3c56b5121edd56728fd5ae52509942ce652ebc341a6ea452304350c6f9312c4d030b718d3043d47023acc6e2018fad6463833449928b3f5c1cca1cb5c34f9b0bef6bb79d65b22a53b790a80a974a93cd44516216a926bbb5c817f8fdab173fa42f2633d560eaaecbad7eb2a9518785a4c696990ad35c16e64d7e485f6cc1c51f2ede9a7d93f7b34a53989c2794d22f9a599a9b2a7395504aa3f35989fa6fce0bdfb8b4d6adb46f9ab60524cd5b712d5d2f2eb427b9e4b96b1368397f9b59a1f3c54e259c47bbf54df3e2aae0146b633dec095b0c9767b93b17b6601d834d9f54fa2b3343f3e9a94972be40f22d786314d4d6144d8e14797ba71ff76173cfa2f038068ba218435317c223085d40810a3d6ec1e9c1d939885ad2d45f31f7d08622ccada92882e5356a28841733e130853579148c496eb4e69906fc02a19841eca17075de4a5d82d0422d7fc3401079b11eed06e191dc1408345480d1dc3357a274a4dbb52cb04893e4f592d7423c0bf4422a17145d673c338d8f1c5553b1164dee1b8b63d6bd15ca3bd4406eaa5a7839531809c12f6b4c6ea45f30138b578db458b4dcb4a8d08d591c53ba312f45686d3c6f529726c97b0faea9c97b0e2e4a63ab81977b6ffe2af56f8b86fc5e6d81a7b5122759d50a69033b70a642689c20e52af40b53b814de7659a5d701a4ce5553602b15e6c6826e94e2a941730717ee4aa5c78d52ff0cb386a1a7842e39d2ebcb326b6d99b92b956dd08cd7c6a82d30162ecac662d930f3f4219f7e71812e23baad84d645c6c42f35e6a4fa4c3899c3ac91ca53122c4de09426c9b4443da5fc19422103a95dcd0e982d99c78db19760e670867e01670b54baf1fe8f0e2e8ad9abe0c147f569295f651ddd5670ddfeecd559f07c2b357b4ae6de425668c59e29d0fed141697e7546432df24b5122799ada8f8a1fcecc02dd569a24096567deadd1add2e8a4af204e5030808f6ed45e488d055070cab047d20cbf30511668d3620667574a7afc61b06f3f3ada33c1b45c43d676dedc28656e8822d8254da6d3a9bb52c9dee9c1eef9019cefbe3e3c809150b3a672a364330100b8d8a5e6fbe213c0fbe3f383b707a7f0e1f4fdd1eee9cff0f7839f61f7e3f9c9fbe3bdd383a383e373383e3987e38f8787e330f55c7a859fe8e7f14fbba77bef764f375ffee9c5d67db25deba5f324a295b04ef0e6e4f4e0fddb6396b7d953535e7b73707a70bc77700623c1dd6eb446917cf7ddc931ec1f1c1e9c1fc0f109ecee9dbf3f39869363f8f8617f77d8976c9131928d8d8d0d38b742bbb9b115ed376f68a03409052eb031da04b24abecbb20b78f109a28de01bff93da638976bd9376d036cc857208505b5909bbdc066f1b0400d178d33572a3b61ff050a8b761f232340a9c8b46f96db8f8947c1794dba71930a524b43d8aec3f5fe2f247f2e0e7ce853f8624b21d57f423ad7a3bbae5c711503c6f8708f92c8b1114b3ed51241d5106f766d6ccb74752fb1f5e8d5f8c4dedc7944877ba19d360b3979f8083e35b2d06a0af85cd17c28608faaacd623bd82c36d8662d8f359bbdfcd38b87360bcac5ba168d162dc343c12e6b51fda393bfe1f6cb3fbde8cce48932d8e83cfcec2d14588f5ff6260ad4d13eaf3e411bc5fffe987ad23ebf23a682726b41d5464ed4fb89d0e1d13e7622f1c3e07935089e6ece34b9e36d4ae9b5cd6d0440a80c0a2ef1047842a2b5e87c0778a483da38c7e59f4bfefeee09e4b62960de6806316e0c94ce612174a1d0ba3154e21209d08fdbf4ecd05ea3056111c4b5905c7453d85b607e098445b88c9b39e7da8b7ca022169185fbb4b9d1d5f5c959e8ebeac36ba9855dc27bedbc502ad488e974fa7af7ec1d0dcbd0df03b34db7308d2ae8a010c7b02080f6df5969b299d46109e01a8b5d1f59426a3a5258a8855fa4c933aaca257a9834f0140465f50a73a3951145670e2e3f5df5499ec10db179a428aef1caacb8c9020c7f58d392788a0945eb2154bc2713365b07b727a6af174f28a4dda215a386c9c45d298ad11d17ebe82f546dbe9b4c08d93a6f6194f68a8dbad14e7625a486ae9b1142dba00dd13702926a9b1c976d83c2a6d7aeedad4cd1284251417a382338ff185df73b0667371483774dbfc9bcf23b4e535074ca455b4f0a611e7692757b5dafd1de58e9919d4498ae68550c9eda0c7db535393a175cd2062d6127fb0ae41cb4f17db89233f2a23741f22cc46cdb6449615bf467e29c779420cbcda4eea66e6cc0cd023528d1e87c4189bb0f7fbf10e1ac70f68f430a20364d8fa4a4ebd8c69071a2e26801e1b81d94206d6b48d32c1d8406006da27e09acf4498d1a04ccacb971686957ae56cfd2e0a4b37c8115dedd6d6759dff9ce387f77b75a918510dade0f04f2fffce2ee6ebba7a43ea2445ddcdd65ee469425da4cea02bfa40b5f2996ffd1216fc72c6facca684f4a242de6e8f3055c4bce9615c16225352621f585133859422d8cf3db7f7ef1e71759808309f1798a2240af845373942e4aa4666eb4330a93d52a7d8bfa1daafa2e6470ce77142e52977cbc6d3772dcc0848abb18a6f48ec22d43d085a366c10ba0b420dca54b93e73065772f50d553988092cef7691abcb0257ad7913127a25b43c831d4a62925aa69471c3d4be4b609f14119a40df4989bdabd7076a538c68ad930a8baf870502b910779e156c5ad8d27277e4111131406fc420b210b1478dd2e96ac7710c987c68b67e8c19122c67b97b462784f3b71538a72ccbdb1cb14deb4a7ec96e1f428fa600ab9d0b4fd1a178acc9add8669804f15e94cb845b2b695d9ebf1622116c5b5e41d1d7fefa4c289a5d10559e449bd93e7d0ea99003c271de78deafad87a31e5104f2a9eae8fa5560bf4790aef9d6b30b87d4acb2ca4ab955872585191af1b4fe2d2527a596a6383c0527a084d96559a9669f21c4a9356a6086406626a77e89b7a0cb5700ea66dc29ff24d087370e83dab3af0459c1a71184c87e5a1354af21c4e0f76f78f0ed22a88fc10275b144585c97310759d853c9251014b4bc374018bc05ba9e18c47c70127c5f402524bdfed3ad4bc93442db3bf84332be1b4bfb6bcf8ba89c115f9d41a45782a790e85308fd21316eb60588c615a3cc33891f31e2357b440e3399902d5a3bcc209df81c5daa243cd561460cd4dcc22f9a2df0c3c9f33d6c6c6e0be25e2b2e4395cbc3d393d62fd589737ad929f36d38c22f37321cc672af669556c11fdd93f0efff9afe8dd95fad2d2bfa3bccbb4ef22eeec4829d7b66447b4de6e8457df0e7d88b819f6714e4e924677842da666dab0f9baabc088171a2f95fc0d439123efcfada8f0c6d8cb31f0e28b199393ebcf6e6274f62e6ac3a33079436111402b194e3e7e3f524a3d298d9639fd0a262316e65162e66ef81f267dfb2d376c4418d0c6e30a9041e642998c89b622f63e0b176a58c07e8c0e97ec35d6a2f66ad98f8e93091c092bc5fe6bfab53cfbc76132810fc6f9d262681cc9dc1a67e69e726fdc4cc92426e224f9a08426781a59261338b1228f50675d0b385fd6e892e4c8383fbc04a40388eb34628f1c2ddd951ab78ab8719bf7c96d4767d44ae3b550e330d4d6ba2607326ce524c910e95e921745d151868b3b78bd6c33d0986386ce8c560bd5d131330654a22098a63b4488450aefe7ad8c164e7ad440c7b7a2e0e015aa5d6551848bb9c6b59990326590b2835fbc1529693f5d4332606ae2928668ef72659c1627f42a86f2bc7654e89e69a8c0a40ca4a6f1bc20ae716740c005823ae32db4f67c9b1b4f803d1f6fa86ef5d3d2e4352a7313aefec88b74848db386561c835b842ab836622c4ce345c63465d045abe29bb25512ceea2377a53e931947db308ab4a3713b589a6e2c5c43f4436dc6e808664b8f6e3095cf32dd28df0e9fdde3519acfed95742f27de48475a26bdebf1e26e712d748e053b2dc469fbd411f04c5b1e84927e490655624967273ec79a1bdd15c9b9b195f0ec36ebc2abd9ab18130c123f048eec7a35f051f70e309d4cdade7d6977a671324cc02fa4bb77ab1ad049179ff798a670def1a7a047c9c86ec6f7e3619bf3b3062f38214c1ee22844f320e820cdaae5207ee0bc53a50b3fac66c83ba65f13454a5009696fe9921f76f8f00f377c73e06acce57cf9d585f399aabd6be1621a6d9d26644fe1c29df364825f30dfe90f46ecc5088dd77628ebcd17092c7648ee722beb8033885be311a40fcf51716821dc0012f03e63102075f03c694cd30d9b9a7a19648574c6868ed61384301d816a54a6a660f106f2c67953c9f88635b86032f3806de167d340ceb653c6d4e017d63465881c3e71d0362685e21b8fbe3697984c5b6c714e436f184e1b0bd0f57357642e3408e50cd468694940ca850727704dbea003697559483b86dcd4cb3178d3e48b31d437451aa04c7b32181e6e289db8f6b5af7bfda32584d8b54da809bdfe69721817d8ac0f8ce1129794bb7ae31302e3f55e0bd5706665c0f25ecf4d0afc3a190287f36ebf5dc2e49e3449a6dc450d07db3b24f72264a84fcf3bb269f2883d3707131f9d350ed28e4585e3ce003453734f40f6fbed89a2ed20029a126f82c731bd9cf020cc8c515bd370ec5dade80f58a14b84679d2c18c7062d271d287977473970b57a266944eabcef616b6eef80371feb1a6d64909e773c271de93c2e0088beb652fb398c8e96dfbbb43423089c883a90c303b3c1b3a14e0f04c1286c88b434a9af6a35829147e747d00bf6b6c1bbbbd56a02a80b5ac3b43fe03f1989f703314dd676c2e6bfd13fe30e6760ebaf81bb8652bbc556c5fa62fbfe51bc2d0f7f3fbd583ef78ae101752093376fe09d09855f32fec5bda3aff0e46d1ef7d01abf30f0283f6ea7fe8bff1ae3f6844b989f5fdc1fd5babef99a772913b5fbdbd89e457074fc0886f064bcd6784208b319a569f6395a3efc33949bace7485337aaffb0407bfce203789d2def1777468c0146cf965c1406b032ceddf94b8787ffda16c0507e7a9cdcde43b618f9691db872d6c206411d2a08c6e3901b3c06f323634b9464191caed597bea6f036e7f78607c9d74ad370d9af7a14dad124ab551a71960b275c4a0c59f6302bfcff25d3a13affe799201c11faab26060b840cba4bc3782e685cf7ac43a86490b60629ff12976378160a20e5fa2e9f7779f9fb8dc9abffba1e3129dcdddd1b9cfcf0e21cbedfa0f1c024fe43c9b5cdad49bff7e2970c5a54ed05aa984906c77c61c868c6a11f567b3ee0ad452fdf550d919debb8229ff486bb84efa6c25d15aacf41f0ce6a355aadd2bbbbd1dddd94b5881c5a31c4a9b8178829fcd4466a77e9333c77c69358afb7091fc70598cae11e81509f0dc845342a6cc9f710a428517222d9691cda29f1cd8dfe35ca5c43fcc33531349b4e26b4d9a90ba784b1e05ae20ddcb4cf088134a2e81945c26d77db0300b7c09b094215bd851086c9ed6432e9fe24b764da46f173f46d38af10ed74b5ea0646dc4d19700a709bdcd6aab142113ebd3fa51fb93fc7c777f675fad0fb80d61c9a1b7ca850dbff903e409487f4a1ff09fe7ba242b527dc236add277842e2d738dc2378c8e14c8b4b7c62723f766f5e923cb2f1da12e892dedfdb60b194ce87bcfe39143f8a0f4e9f444213df707a22b1837bc7c09a9824833099f2261bdddda5fcf78815825b58ad468f481a7166a16981dd1393bbc16f65017d503ec96c40f3cd6c1f7afc69f60f697f8798fb61f13531f7697f879841007d4dc280ec3f642a4ec09fff4306fbddc27ebfd9fe172236c16278844be197c8e89711fc32fa65b445fb3d72785c703ff92b2ab4632318314b08296463038e8d4717ee8eafd4188edcdac56db8670f1f39f0e595e85f2b2af482e12bbfe1f283585bd0af1ab4cbf8616ef8d8317e05cc35b1f1864e9896bf361db7df01f1319e48bbef4719001126e0cb0f63abc1d7ae69320935cd05b151396df4c479a10b618b35c69bc727c7ddf744f1130e0be1fa6f6b00085aa0105ecf8af0b4b9ff1a8ed08b70097e68f84be0e496bab9b42e6b2eaac7adde700b1fa2e8bfe3929abbb4e4f7ed92a9672f7cd57b881a6ebb6be19f0861dd42cedf4f4351a8f50afdd89fe4367e820270bb647596f7ffba85e52d2c93dbbaf5e9bf20d5c96db57457ea1b58560ebe81307caef3135a4768e49d74049409fe9f22ffaf09b18701e5c606ec19edad9c35de580acc0b3a34fefaeae5e3cf27ede0164c0004cc248143a12f1970b6831c440be9c2fb1fb126909724678850198b7dbfb16e1b2e86cdc71f82d63e202aada8172e1bceda4afe270000ffff7bc45e969e310000", + "a2d5586edb9ff79f53fe610c4e91de3d": "1f8b08000000000000ffc45bff6fdcb8b1fff9f4574cd739d40e76a524875714beba85633b89517f496de7d0832fc872a5592dcf12299394ed3ddbef6f7f98212969fd25cde1f5f51d9064490e87c399e1cc6748dd79aeeb1a95fbbc097ff92bac9f2da405694140890a8d7058c05c56084d85c22260211d58dd9a1c412a48338775530987762379c06abbaaa0d6859ccb5c38a9155ccbaa821942a5ad1bc352b7b0105708334405d7c2282c1ef1d848d6d648922439ffc3f981cc5159fcbcbe70aeb19b5926eb32b50b89556153a9b399284acc02d564bb11f902bf7ff3ea87f4d56456b598daab72a39fac1b547e23a9366556f96936f3f3263fa4af36e0fc0fe7eff5aecefb59a52e74ce134ae916ed2ccd759dd95a549542ebb212d5dfac13aeb569a3e26adf346d03683567c495b4fd72be3dc925cf5d9940dbf9dbcc08952fb66a611d9a8d6f9a17760527d868e360479862b83dc3ddb93005cbe875faacd05f99e99bcf4f4d92b305926dc1695d416374d1e6489eb773f26917d6770c0a876330288a31b44d211c8250051458a1c30d38d93b3d03d1489afa2be60ea22bc2dce89a3c585ea18242383113165358598f9c31c9b5523c53835b201433083de4aed619a94a104a54cbdfd013045e2c473c203c92eb0281860ad08a7be695282dc976250b2cd22479bbe4bd10cf029d9095f582ae329ee9d6058e555bb3146dee5a8363963d2eca275443aeeb463839ab3010825b36985c4bb76026062f5b69b088dc94a8d18e7939a6b463de8a504a3b3ea4364d927d07b66dc87a16ce4b6dea81957b6bfe2ad56f8b96ec5e6f80a3bd1227593715d201b660758dd05a41c2d5e816bab029bcefa24a2f034895576d817155986b03aaad2a9eea25b7706e2fabf4a8adaa7ffa5943d7ab842ad9d39b8b328bbaccec6595add18cb75a571ba00d9c97adc1b265e6e9633efde63c5d46741b09ed8b9489370de624fa4c5899c3ac9595a320586acf294d9269896a4af1d3bb420652d9860d305b328f6b6d2e40cfe114dd024e1758a9d6b93f5a382f666fbc059f942752bec93aba0d6fbaddd99b536ff9b86af6dc9a3b0b59a3113bba40f3470ba5fed56a058dc82f448964696a3fb9fc7066e6e936d22449283af3690d66955a257d06b1829c015c30a372422a2c809c53fa33926678c34499a74d8b199c5e56d2e10f8373fbc9d299f1aae51cb272f2e6baaaf4355178bda4c9743ab59755b273b2b77db60767db6f0ff66024aa595bdb51b29e00009c6f5373bff80cb07f74b6f77eef043e9eec1f6e9ffc0c7fdffb19b63f9d1def1fed9cec1dee1d9dc1d1f1191c7d3a3818fba967d255f8997e1efdb47db2f361fb64fdf59f5e6d3c24db364e5a474bc4155609de1d9fecedbf3fe2f5d67b6a8a6beff64ef68e76f64e6124b8db8e562892efbe3b3e82ddbd83bdb33d383a86ed9db3fde323383e824f1f77b7877dc9062923595b5b5b833323949d6b53d379739a064a9d90e3022b230690dbe4bb2c3b87579f21e808bef13fa91c9668563be9046dc25c5416011a236b61969be04c8b00205aa7bb46aeabcd473c2a549b3079ed1b05ce455bb94d38ff9c7ce785dba51930a520b4390aecbf5ce0f247b2e097ce843ffa20b21976f423ed7a3398e5c711903f6f7a0ff9228b1114b3cd51201d5104777ad6ce374752b91fde8c5f8d75e3c61448b7ba1953afb3d79f819de35b3506a0ae84c917c2780ffaaace42dbeb2c34586791c78ace5effe9d5639d79e1425e0b4a0b9ae121af9715affed1cadf70f3f59f5e756a7244e97574e67ff61af2acc7af7b1579eaa09f379f217af1bfdfa79ed5cfeff0292fdc8a5345cf09723fe33a3cdafb4e207eec3c6f06ced3cd9926f77c4c29bcc6d8460084d2a0e0144f80c7075a83d67580475a68b4b59cfe39e5ef6e1f436eda02e6ad621063c740e11c164215151a3b865a5c2001fa710ccf16cd151a1006415c09c94937859d05e617405884d3b89e73ac3dcf0722621158d8cfeb6b5d5e9f9cfabe2e3fbc954a9825ec2beb4455f91c319d4edf6e9f7ea061e9fb7b60b66e17baad0a2a14c2181604d0fe3b2b753693ca6f016c6bb0eb234d484525858146b8459abca0ac5ca283490bcf415016afd0d7aad2a2e8d4c1e9a7cb3ec90bb826364f24c5155e9911d79987e18f735a12aa189fb41e43c5076bc27a3470ac98be9e3ca1906683768c0a26137b59918f6ed990477fa16cf3dd6442c8d63a03a3b4176cd48d766bd7422ae8ba1921c4061d88bee191546cb25fc606b94d2f5decad75d15684a2fceabe46b0ee29baee7770ce6e2838ef8a7c9379edb6ac22a7e8840bba9e14423fee24edf6b25ea1b936d2211b89305d1145f4965af77d8dd1395aeb4d129d96b0937903720e4abbde5dc91879d1ab2079e17d363679257f2cfa9a38e71325487333a9baa96b6b70bd4005956855bea0c0ddbbbf5b085f2b9cfee3801c8855d32329693bb6c165aca8d95b40586e7b2148da06d2344b07ae014087a8df020b7ddca0020133a3af2d1a3a95b7b72f526fa4d37c8135dedf6f6659dff9415b777f7f7b4b1a4288bd1f09e4fff9d5fdfd664f497d4489aab8bfcfecb5284b34995405dea40b5757bcfe278b7c1cb3bc355546675222493147972fe04a72b4ac0916575261e2439fafc04913d5425bb7f9e7577f7e95793898109fe7283cf44a383487d54589d4ccb5b2bac2e4f6367d8fea0356cdbd8fe01cefc85da42ab9bc8d07391c6042c59d0f5378476197dee97ca959f006282c087b61d3e4254cd9dc0bac9a294ca092d6f5611a9c30253adb913127a25b41c8c1d5a62905aa69471c2c4be4a6f5fe4111243a7a884df12c9c5e56ec63c56ce8549d7f58682a91fbf5fcad8a5d194f8edd823cc60b0c78431b210d147815374bdadb0be443e5851a7a5052047fef825670ef69b7dc94bc1c73a7cd328577b1ca8e0ca787c10653c885a2e3d75a9f6456f4360c035c55a4336117c9ca5166ab878b85901457827730fc834a85034bab0ad2c8b372272f21ca9900bc2419e76dd5f5b1f642c8219e943c6def4b510a74790afbd6b6e8cd3ea56d16d2369558b25b51926f5a47cba5a574b254daf8054be9c03779ad5247a6c94b28755aebc2936908a1dda26b9b3134c25a98c6803fe59b10e660d1391675608b3035e030980ed343544af2124ef6b6770ff7d2da2ff9314c36288a1a9397209a26f37124a30496969ae93c1681f752c1298f8e3d4e0ae105a492ae3b75a8f8248946667ff1352be1b4bf465e7cddc4e08a6c6a7445782a790985d04fd21316eb6058f061da3cc33891f319235344a0f1925481d593bc7c856fc16063d0a2622d0a30fa3a44917cd11f069ecf116b6d6d70df127059f212cedf1f9f1cb27c2ccbbb28e4e7f53423cffc5208fd85927d5a171b447ffa8f837ffe2b7a7b59dd44fa0f147799f643c09d1d29c5da487648fbed4678f771e863c0cdb08b733292d4aa238c989a69fde1ebae02035e689dace46fe8931c597f6e448dd7da5c8c81375fcc989c4c7f7a1dbcb33751748f42e72db98507ada438f9f4fd4829d5a4d44ae6f4cbab8c58e8278999bbe67f98f4fdb7dcb011a1471b4f0b400a998b4a674cb411b0f7a9bf50c302768377d864a7350695ab96fde83899c0a13052ecbea55fcbd37f1c2413f8a8ad2b0dfac6a1cc8db67aee28f686c3944c42204e928f9550044f03cb6402c746e401eaac4a0167cb066d921c6aeb8697805480d84e22b6c8e1d25e56e328881dc7b84f663b3ca5561aae855a8b3eb7360d1990612b074986480f82bc288a8ed25fdcc1db658c4063f619aa198d125547c7cc18508982609aea10211629eccfe31a114e3a5440e55b51b0f38a2aeeb228fcc55c6b6324a448e957d9c21b67444ad24f57900ce886b8a4dedbbb5819a68509bd883e3daf940add330d25989481d434d40be20ab706049c20a833dc422bc7b7b9a102ecf9384d79ab9f96266fb1d2d7feea8fac48256c9835d4e218ecc267c195116d601a2e32a629832eda15df94dd26be561fd9cbea0ba971b409a3403b1ac7c1527763fe1aa21f8a11a323982d1ddac154ae65ba51be1d3e7dc0a3d45fe29574bf4eb8910eb44c7adfe3c5ede24aa81c0b369af7d3f8d4e1f14c4c0fa2926e490aadc4926a27ae63f5b5ea92e45c9b5a38369bb1fed5ec4df00906891f3d47367d35b051f70e309d4c62efae345bd3301926e016d23eb855f5e8a4f3cf074c5338ebf893d3a3646437e3fb717fccf95983379c1026f77ee4bd79e0749066f572e03f70d689d2b91fd633e413d3ef893cc58b8474b654c90f3b5cfcc335df1cd80673395f7e75e35c53c5bb164ea641d76942fa14d6df394f267883f9565f18b11503345e39a12c375f24f0b243729b1bd9789c41dc5a87209d7f8e0a430b61079080cf198300a9bce549629aae59d5d4cb20cb87335674d09e208469095463a51b7216a7216fadd3b50c6f58830b263df7d8167ed62de4acbb4aeb06dcc2e8b6f49ec315071d631228bcf1a82b7d81c934628b331a7ac7705a1b80ae9fbb0273a1405456438386b604249c7f7002dbe60b2a48eb8b429a31e4ba598ec1e9365f8ca1b92e520f656265302c6e289cd8f8dad7bdfed116bcef9ad6e7845efe3439081b6c5707c670814b8a5dbdf20981f17eaf44d5726465c0b2afe63a057e9df48ec371b73f2e7e724f9a2453eea28685cd2d5af7dc47a8cf2f3bb269f2843ed707139f9c35f6ab1d891ac79d0268a6e21e8fec776345113b8880a6849be071082fc73c0833adab8da92f7b6f6fe90f18a14a8417dd5a300e0dda4e3a10f2fe9e62e0eded0b492352e57d0f6b73730b9cfed434680283f4ace339e948e7610340f48d91cacd6174b8fcdea6a51e81e744d49e1c1ea90d5e0c657ab4108cfc81484b9dbabaa946307268dd08fa859d69f1fefef67602a80adac3b42ff09ff5c4878e98262b2761fddf689f71873330da6b60aee1aadd66eb6275b37dff28dc96fbbf9fdf2cd7bd6258a00ed6e4c3eb7967a2c29b8c7f71efe82b3cf9988733b4c2cf0f3cc98fdba9bb715f631c2b5cc2fcfce2fea4d4cdf5d7ac4b91289e6f6d7a16ded0e12318c293e15a63b808cf8df93d7cbdc0d13a247f984ce2ab378518ca33bed50847d0d3a6f031fc6264dc814d90042c0b2a6585ea5ece1b833962b84482c88b739223b3f178812ac7140edbcac986b34b2f409862238ef5395412b87dc7511615736744e18f17fb537781d90bd855a49e0a82dfc6c365bb29d669d3df4d36867242afc350aa0b1f8723fc14dd5b07df34b5f3b9bc79884d016f1c2a4b80f949e19f169b5270cf22bef97721214ea0ce1589e20c3aac4d1370b747ddbc9297315d09a8ec1da334cdbe84c3e8ffa198d68f6633915fa02ac2f0a0d97b4b2a0b14770188d895a152a7b6ad47407dd1d2e9cbdfb3489cf5549fbfff5995e5e508987b779c92d5d4af9bb6eabf97510e6f9cafc966cb8798950b215f1dce968c7506d55298bbf597aeccfb6bc4751e55f5e55fb45a2cfd9e97810161238c5fa803bb3e2670241d7ce3c06fe79128c9323858814d3d54e2ecc5cf688f308591ba65345bdb55ef269ae4f6360de583f51737a4d82c7b9cecfeff30c2509cfff304e72bdffe0695313001deee2e3c94bbaded8e2681ed41361e20990b5c8ee185c77504613a98d2c18defd7266ffeeb6ac4a4707fff6070f2c3ab33f87e8dc63d93f00f61860819923ea5840f7494a8e3bb809849aef9f81e9c41ba45378c3f7c6fb1e2bd7c053b2c586cc715f90263784af8cad55fc162f5c52fbc757b3bbabd4defef47f7f75396227088cb10a7e28123a6f053f4d4ee2e73789d1282772fb7f6df7c2efb4019f07d1f0dc844342a4cc9d76b2428517230dc6a2d9a29f1cdb5fa3526d061213bdc13571cd3c9840e3b75e1944a07b892780dd7f175cc9386e270469e70d75d6202c01df061020f0eef42be4aee269349f727b923d5b6157f6571e7cb70a29ddede760323eea6003b05b84bee9aaa35a2a2b2ebe1947ee4e11c173e1f59a5f7bd8f68f581bec6c702c5fec7f41e793fa6f7fdcff0df1135563bc23e21d643826756fc1a8707048f399c2a7181cf4ceec71ecc4b92270e5e447636e9edbd09064b699d8feb5f3ca623ffe0f0492434f11d87275a76709dee59139364e026533e64a3fbfb94ff1eb1407007b7b7a327561a7164a1699edd3393bbc16f6501bd533ecb6c40f3cd6c1f5bfc79f68f697fc7320fdde26bcb3ca4fd1dcb0c1ce86b2b0cc8fe43aae200fce53fa4b0dfbdd8ef57dbff62897530e8df9653f82530fa6504bf8c7e196dd0790f1c9e5eb89ffc1511e2d80846cc127c08595b8323edd0fa2791cb6a0c8776e53dc23f1ff96f77f84e56f48f70353ac1f0953f4de077de98d02f5b34cbf0bdb9ff86377cdcce39b1759a0a33c31f518fe3e76d7c3b45a4dd67d10c800813f09d9e36f5e023ee3499f89c66fdb24138a5d5c43aa10a618a15c6eb47c747dd6772e1cb2403fe567b63a5eef240c13f0a17fec57ef72d1ca213fe6de740f307eec91d75736a5d369c548fa2dc70071fc3d27fc72535b769cbfb71cbd4b3e33f563f400577dd6bc74f84b0ee20e7ff2d008aa25acdd04ffd49eec2975500774b1667f9f0af3b58dec132b96ba24dff05a94aeeeaa5bdacbe81656de11b08fd57683fa1a17a153e482a8a9704ff4f90ff8f9bd0c380726d0d76b47246ce5aa70d39e6b9a8f0e6d737af9f7e158c831b3001103093040e85ba60c01907d98916d2fa676d624d202f494e11a1d606fb7e6dec269c0f9b4fbf6fae7c17571ad12c6c369cb591fc4f000000ffff908988f975340000", "a42f6cfa87833f9ac66f4293e399925f": "1f8b08000000000000ffbc55df8fdb44107e8eff8a21aa68827cce51fa80824ee27aeda985b68426074808557bf6d8d9d6d95d66d7cd1ddbfddfd1ae9d1fb6e290a2c2bd5c323b33df3733df4caccd30e70261c8147f5ba98c194c0a9998952a87ce459309dc04a3b5c9dc50959ad76c85ce355660a0b9284a04c254520639c915589b2cd86d898dabf19f810b304bf46f4f9961b74c6f9eb3e6abc7fa7e5ead568ceeb7e9c526b10f0f79dac943d053d4297165b814ff15af052b34747a101e2ed314950178a7a5088619c9ac4ab1b1584b4c14080f728e6506d30ba829bc10b94cae6486d7deae9db31678deb82533e2be0b3fe2fd2515355848cd88ad00acedf503e74031b3dcf399fffcf215538a8b2299af5951202dee55703454210c779e57b2ac56e2151a9634b986d6a2c83cb9f02f6a9168ebe15666f7deb89219963396be6745d3c8a4eb5ae33683ea3ed6f31a36624853d41a1e9d9f8395b7ef3035ee348c107ecd785911c2e34e3853bc1dfc7cb1983d2392d4097bfc29616f64659060d262021fc1c897728de49bf8b9a460c1dab37e0d9c3907ae3d39f85d55e60fcf13d3a58487d62ee40ff39f5eef537821b46122453877ee217c84a5310a66370b2f91078946fa80344f97e8294c27939df1b9d4c623f11c04c2c63a9364e0db73e7a63b4f6fdb72fa5f3ab515f7357b8f7ec1c1755a3304f8edec52f1b31b8d34ad34d2d78fbe89f24aa4872fdf681d3a93bc41ada4d0f82b71831403c1578dfdcf0ab58941e9e048411749581b3d061b0d5273e76be2821bce4afe175e4961f0ce8c687cfabd888ed70ece45d1c0dafedec480441ee08053009931d27e1a23a5e3a081de54c37134e079c8f7c505085efa2a0784a622111664949abb18d63150401d6f5fa3812fa475633ceb1e5d78b65f9eb4ffd66d194d2f8090655eed238aa137f9f8bb130bb036c9986cc33f237ac2b266c6adea2200d831e9454f9e602e09e7ec038eba4c20fcfd6b360050d3e8079f112a46381a9f4cf7175672bf1ca39e7b5cafcee7af641bde94b537e50da56601ebd414a4bbffb33f8c4fa7fc29323ea2db18ded6db7671b0c8c367c663f99c27de03f8c77b7068d177d7a0b385fdc51cdaf523530d7d02803d9fba5b6b7f37c3566ebc8f406eaf4414fd1d0000ffff948e6509b20a0000", "abfb948144ce2666cf818384c00cc754": "1f8b08000000000000ff84924d6bdb401086effb2b5ee24b0c967a2f6d21d8b49796b46e028510a2b176b45ebada11da954d1af2dfcb4af2471c83afa3679e7967b40fa5d435fbf8f8119fbee0fa6e6d036c00c1b0e796226b54d6311ac71418ac6d4490ae2d19d623ff10b96e1c450e5375a2ba710eb5685bd992a2158fad750e2b8693106778960e6bda3056cc1e5b6a3deb778ea9529309e6cbfb051637b7f8daf932a9822a0cfb6230ee824293a0da11b015e29a5164d90ec8344991966b2804d688825e93e36ecd2845334af22961d98528b5fdc71a5b1bd73b9126f9dc3b2a4726b5078efd374f35438681294543e55f329c2b353fb21e2e6a3de2569266235d1b66f8fdebfb1fac9ea1b9a2ce4590d7f876bbfc7118df8f4ccb485b174aa97de4d16ac5cf4eee71b84525adcaf0b0e4d85ade586fd07229ad0e83bf2163bd79bc9e8c00673fc9b0ce9603343dea651042c365faaba3e4b86fa8f40df39653083a5043e598b96ff40933548e99053b7ecb0c953d935ec83e5d1f1c637055148511f5f2b27ba4b8d2244fe9844f862339971bc963ddb82be4afaf097f6b1b465cd09c778ceb5f3690d6e70de3712e1bba1e3c2f19af7759a27bf0bde47f000000ffffa7724c4920040000", "ac60daf3c3958d476b98234112b3896c": "1f8b08000000000000ffcc5a6d6fdb3812fe2cfd8a59a1b7900a454eda5c5078d7c0a54dd3e6b0dbcde565ef8020c83212e57023933a8a4a9afafcdf0f33a464d95652a72fc1e64b2c726638cf70663443aa64e9351b73984e13568a43fbf4814df86ce6fb62522a6d20f4bd2055d2f08f26f0bd80cb5465428e077f564ae2403ea171a10642d54614f820b9195c1953e2efcae854c91bfc69c484e3ff5a562cc75f17108c85b9aa2f93544d0615334a8bc15825752db2c0f7bd603a4d32a6f6ff75f861360becf34465bc68467caf2b602ce4c6584991e2af6071eecfba105c56e9d544645639ad6ac375e047be7fc334843e00c0058c00d54c8e79aa64068301383b744773a5a196d752dd4aa82bb4a090c04ae17ba9aeb3b7322b9590a682092bcf2aa3851c9f3f7fa3eb6cf7f000971b0cc03d41c6ab548b4b5e81e6ffad79652a60374c14ecb2e0b48cc15f15ca37571c3266d825abb86fee4ade0aa98cae530353dfc3bd83ce9f5dbc33309d42f246c95c8c937f1efff6e1848d21906cc20398cd7cef8de6ccf0d3a35f1ec39e12d345ad0b2be4881b2df80dff4d5a496b09d18ee942c91e51bf327987b21e276ac2e4dd5cd669993d1e5b4d4c73217bbce08f169211d35cc83e37e9d5dede2f8d98b584e4c8749165c55cce09bac681cc95637bde44c742202773b23eb9e45f1742e68a84ce7c72cf4336e6d911afeac2a067daffe88e251bf30ade71b35b14cd4462bd718167ee9238dc2213d2ec6c3f8012c55b70c8762c3ef1b5d92e2af1c9f1ee31c3ba4b729db3944f67fdbbc30c2336243e518615473c553ac398330f6e8941e20b6da91beb0d06f0fee4e4f0add64a03ffc82665e1a2753e3c37ce1b9571a724fefb0393ea30585a6c5ff022a3c80e529511c6a0113d0cb63737833f7cef575e512272cef45949134b1f2cc8aa0c337505972c6bd251f047e31256cc11254d48e9a1d61c309326ef99cc0aaec1a6543faf65ba401f468b7453dfb3a4301cc13c17271ff86d18f9de74aa991c737846ce89fac6f00ca193170f479098c6a7abd9ccea329db614c931d9d7babf53c02e10f9762fb9ccd04fdcaaefde9e84c120cb8ac190e9f1c15e10a383ef6545b44ad2ceb5491e89b8a9b56cd02fd8eb9d902b261b0bd963a996d2e98a64c9010d55115aec4bacf24ec8350de3ad6195374ade70fdbeddaf13f54ec8d0d9aadf580fb1cc4dd8dab063bc5536b8d5acecba8b7527308a6ce59c6bbf96696bd79eb5f35509d1323f39a8dd549414a6f01c29ded8328876c3c39aa1649a4daaaec0431af13defa2e01237a5e0324cdd70e47b9ec881a64623d824399e133202290adff366c08b8a2f4d85e1f3b32df8f967d8da3c5f5e2d0a6d3d951c2aca75e18fcd7a679be751149d0d71c17314edfb9e978769f26f2d0cd731a4c9910df3d841897ccfbe03ac05851446b0427ce20e7aa8e13945b2e38b204ccd4770f5e18281440eeef1a095a2e1078249e89071d44313a23776ac60e974233c241d9bed49cd47d297d4fd9d1502dfd74eb93ed562584210db0acbe5cd18586a8492309d3eeb7b97eed26c049cf2b845e904b9c5d50246a7e532096a1683766b378b12b016197ac3ccb7af8e657ef251eb99df17a22d8d5710f6e9e3545dddcfb9b2eb398f5db3c777fa45fbadb76aceb203b9e2a3ceb75bf837b69c8820a4ffb1454a3e5b62c4ea645fe9c9efaca879489c116d7389211b04dd6dbd896dcc7676cd753b1880154765ca18b63663d8d98e703749cf5b8c3e7c1df7efde2d38edab52c98a37b17ad3ad64485bac5c62b8409df16d9ffcca7475c58af026f2bddbe43d6719be7993636ec280a44bb3717257f220868095652152865b4d9ddc4f905ea1ca66549b7ce355d02782a5577c03056985a93d906a23c5314b4c7a86a8d312d223755bede6394f0dcfc27bc0e90e4db33d0f20ec92ff65c0a2fbd1aeaef8dfd2d6b5c9e3b2cec9fb109f6d9e9323ceb2dda20875f25a6577d6f3906235a770ad173c8f0c742a27ce4424fb269a27474b4605e8a3fc6e050daa330f1957320e479bbe57dd0a935e11c1d4f75256d1c942c6d4628679abf50765f6552db3a1ef35126c21981cd3d36b96b9f51e96732a31b39d28e71868feaf16492ddf3e1305ff6af50e64c5b5f936b26cebf96d64bd6699ad10d61194f19cd585598774e67bb6a66f7b1d74586c7286b6d14586d8f79a6e6588be92589f8c62ebcec75cda30baa53724c7177fc6f147d454871ff8ed627f450ede8c927777ebb5d82d8c51b8ecbe5fa92e1627d459858eb855d3455d8979e65448f32a2c7b0ac518aef99d7b334510d648d87d2189ecd890866595bcbec30d0caf39a585accd1c2baf1c62b2af9d570f65105a2d14591437c9e49e99d91296ad9d75c16ced7c4b345b3b9f83b3b5732f9eeed432a0972fd605f4f2c5b704f4f2c5e700bd7c712fa0eed432a09ded75012d16405f0b08cb9c8701ed6cdf0ba83bd5054465d4e7d134d1fd245036b6562008e2efd77fade87fd2e0ef07704fd01fac1bf34f1cf2fd20ee8bf4837503fd89e3bc1fc57de17db06e747ff3e0fefba323225b55ff98d45b47ffa65f5b0340bb1ee96a3be74e423c3dd87b8205eddf60e00e084154c0a8efa56edb281873e33a71217345c7e9ac1da0eb9de934d973373cb6589b5ff80c06f08fe37a3261fa0e17f86241707907749e47124fd8b882f68280860ef63af37b743d551282ef066b374d7969803a181a38d42aab53de1da10ede2a0625335774626e74cd211059e0ac93a6bcaae0c5e6264cd5e59f3c35b3be9bcda4b9894326aca76bcd61fb734c6d89b8c4b6bd261b049db62786ec12ecc501d94b642095811ce760c3b56a1534e4549fd25a732a72520bdc1d30d359ed944c3483b33137e7388b4e0fc174fa2ca9b8bee1fa38bde2a8d97030980fbe579599cda6539183e4d08c1e2a6de0d5e66c369c53e21852d25931adf8f1ee5300ffd9d82dc5c669c5f5b0aeb8de7af1d2c69ff5997bbbfe95839a9e20a540c4921e9be4d5f3c8c8f73deb160b611ad8336b9c75196b38ea3b1fa41e23b067d5fdd767cd6d5df4537fe26b7b8e189a8e4547ed6cd3a2e356c7a0ae518d853be23352f49cd4fc415d3f24389f18db7ce4615053e38be1970b99d9701bc2dfaa20b64112c1b20e0ba74e562469e5da95366fcd6faf7b239d41212a032a872c2b80b7c40fdd5aaf9fd6aaef21bf27c9ade6b5a787bd46dafba294d649074f97063e97025af37eef5cf01788f6d5485b88f8f9892142082da685cf46463061d73cecf97a0411de300d665242fb4589bb9feeb91d6c2f0497ae07e9961c658ce04727052122d790bca415329b05b1efcd3f0c19423068e697ae14e17f60d42fea966bc7b4f821c89770baef3e1ec3da7ee6f118a6f6b38ec730753ee340367afbae580efdc14ccaf94718317d6274cfddcb3b6e5ac270791fa22537395b263887113a863fbfcbc5ccfeff000000ffffce582ff2e4250000", diff --git a/readme/main.go b/readme/main.go index 8dcf733..1909ca6 100644 --- a/readme/main.go +++ b/readme/main.go @@ -32,7 +32,7 @@ func init() { goopt.Description = func() string { return "ORM and RESTful meta data viewer for SQl databases" } - goopt.Version = "v0.9.26 (07/31/2020)" + goopt.Version = "v0.9.27 (08/04/2020)" goopt.Summary = `dbmeta [-v] --sqltype=mysql --connstr "user:password@/dbname" --database sqltype - sql database type such as [ mysql, mssql, postgres, sqlite, etc. ] @@ -137,7 +137,7 @@ func genreadme(conf *dbmeta.Config, templateName, outputFile string, ctx map[str releaseHistory := loadFile("release.history") ctx["ReleaseHistory"] = releaseHistory - conf.WriteTemplate(template, ctx, outputFile, false) + conf.WriteTemplate(template, ctx, outputFile) } func loadFile(src string) string { diff --git a/release.history b/release.history index 3ab586f..db7d854 100644 --- a/release.history +++ b/release.history @@ -1,3 +1,7 @@ +- v0.9.27 (08/04/2020) + - Updated '--exec' mode to provide various functions for processing + - copy function updated to provide --include and --exclude patterns. Patterns are processed in order, an include preceeding an exclude will take precedence. Multiple include and excludes can be specified. Files ending with .table.tmpl will be processed for each table. Output filenames will be stored in the proper directory, with a name of the table with the suffix of the template extension. Files ending with .tmpl will be processed as a template and the filename will be the name of the template stripped with the .tmpl suffix. + - When processing templates, files generated with a .go extension will be formatted with the go fmt. - v0.9.26 (07/31/2020) - Release scripting - Added custom script functions to copy, mkdir, touch, pwd diff --git a/template/GEN_README.md.tmpl b/template/GEN_README.md.tmpl index a69478a..2a13318 100644 --- a/template/GEN_README.md.tmpl +++ b/template/GEN_README.md.tmpl @@ -229,9 +229,16 @@ Loop thru map of tables, key is the table name and value is ModelInfo. Creating ``` ### Example - copy a file or directory from source to a target directory. +copy function updated to provide --include and --exclude patterns. Patterns are processed in order, an include preceeding an exclude will take precedence. Multiple include and excludes can be specified. Files ending with .table.tmpl will be processed for each table. Output filenames will be stored in the proper directory, with a name of the table with the suffix of the template extension. Files ending with .tmpl will be processed as a template and the filename will be the name of the template stripped with the .tmpl suffix. + ``` {{` {{ copy "../_test" "test" }} + +{{ copy "./backend" "test/backend" "--exclude .idea|commands" "--exclude go.sum" "--include .*" }} + +{{ copy "./backend" "test/backend" "--include backend" "--include go.mod" "--exclude .*" }} + `}} ``` diff --git a/utils/copy.go b/utils/copy.go index ade9bb9..6efbb71 100644 --- a/utils/copy.go +++ b/utils/copy.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io" "io/ioutil" "os" @@ -104,7 +105,8 @@ func DefaultFileCopy(src, dest string, info os.FileInfo, opt Options, results *R // 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) { + if opt.ShouldCopy != nil && !opt.ShouldCopy(info) { + results.Info.WriteString(fmt.Sprintf("CopyDir Skipping %s\n", srcdir)) return nil } diff --git a/utils/options.go b/utils/options.go index 6d9d3ef..4e3b3b0 100644 --- a/utils/options.go +++ b/utils/options.go @@ -23,8 +23,8 @@ type Options struct { // 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 + // ShouldCopy - return bool if dir or file should be copied + ShouldCopy func(opt os.FileInfo) bool } // SymlinkAction represents what to do on symlink.