diff --git a/config.go b/config.go index 06ab571..4af2518 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ type Config struct { OutFolder string BaseDir string ExcludeTables []string + Tags []string Debug bool NoTests bool NoHooks bool diff --git a/main.go b/main.go index 2c857cc..db3043b 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ func main() { rootCmd.PersistentFlags().StringP("pkgname", "p", "models", "The name you wish to assign to your generated package") rootCmd.PersistentFlags().StringP("basedir", "b", "", "The base directory has the templates and templates_test folders") rootCmd.PersistentFlags().StringSliceP("exclude", "x", nil, "Tables to be excluded from the generated package") + rootCmd.PersistentFlags().StringSliceP("tag", "t", nil, "Struct tags to be included on your models in addition to json, yaml, toml") rootCmd.PersistentFlags().BoolP("debug", "d", false, "Debug mode prints stack traces on error") rootCmd.PersistentFlags().BoolP("no-tests", "", false, "Disable generated go test files") rootCmd.PersistentFlags().BoolP("no-hooks", "", false, "Disable hooks feature for your models") @@ -114,7 +115,7 @@ func preRun(cmd *cobra.Command, args []string) error { } // BUG: https://github.com/spf13/viper/issues/200 - // Look up the value of ExcludeTables directly from PFlags in Cobra if we + // Look up the value of ExcludeTables & Tags directly from PFlags in Cobra if we // detect a malformed value coming out of viper. // Once the bug is fixed we'll be able to move this into the init above cmdConfig.ExcludeTables = viper.GetStringSlice("exclude") @@ -125,6 +126,14 @@ func preRun(cmd *cobra.Command, args []string) error { } } + cmdConfig.Tags = viper.GetStringSlice("tag") + if len(cmdConfig.Tags) == 1 && strings.HasPrefix(cmdConfig.Tags[0], "[") { + cmdConfig.Tags, err = cmd.PersistentFlags().GetStringSlice("tag") + if err != nil { + return err + } + } + if viper.IsSet("postgres.dbname") { cmdConfig.Postgres = PostgresConfig{ User: viper.GetString("postgres.user"), diff --git a/sqlboiler.go b/sqlboiler.go index a7b1a39..aa4d924 100644 --- a/sqlboiler.go +++ b/sqlboiler.go @@ -8,6 +8,7 @@ import ( "go/build" "os" "path/filepath" + "regexp" "strings" "text/template" @@ -82,6 +83,11 @@ func New(config *Config) (*State, error) { return nil, errors.Wrap(err, "unable to initialize templates") } + err = s.initTags(config.Tags) + if err != nil { + return nil, errors.Wrap(err, "unable to initialize struct tags") + } + return s, nil } @@ -126,6 +132,7 @@ func (s *State) Run(includeTests bool) error { PkgName: s.Config.PkgName, NoHooks: s.Config.NoHooks, NoAutoTimestamps: s.Config.NoAutoTimestamps, + Tags: s.Config.Tags, StringFuncs: templateStringMappers, } @@ -250,6 +257,22 @@ func (s *State) initTables(exclude []string) error { return nil } +// Tags must be in a format like: json, xml, etc. +var rgxValidTag = regexp.MustCompile(`[a-zA-Z_\.]+`) + +// initTags removes duplicate tags and validates the format +// of all user tags are simple strings without quotes: [a-zA-Z_\.]+ +func (s *State) initTags(tags []string) error { + s.Config.Tags = removeDuplicates(s.Config.Tags) + for _, v := range s.Config.Tags { + if !rgxValidTag.MatchString(v) { + return errors.New("Invalid tag format %q supplied, only specify name, eg: xml") + } + } + + return nil +} + // initOutFolder creates the folder that will hold the generated output. func (s *State) initOutFolder() error { return os.MkdirAll(s.Config.OutFolder, os.ModePerm) diff --git a/strmangle/strmangle.go b/strmangle/strmangle.go index 4268907..7faa68f 100644 --- a/strmangle/strmangle.go +++ b/strmangle/strmangle.go @@ -487,3 +487,35 @@ func ContainsAny(a []string, finds ...string) bool { return false } + +// GenerateTags converts a slice of tag strings into tags that +// can be passed onto the end of a struct, for example: +// tags: ["xml", "db"] convert to: xml:"column_name" db:"column_name" +func GenerateTags(tags []string, columnName string) string { + buf := GetBuffer() + defer PutBuffer(buf) + + for _, tag := range tags { + buf.WriteString(tag) + buf.WriteString(`:"`) + buf.WriteString(columnName) + buf.WriteString(`" `) + } + + return buf.String() +} + +// GenerateIgnoreTags converts a slice of tag strings into +// ignore tags that can be passed onto the end of a struct, for example: +// tags: ["xml", "db"] convert to: xml:"-" db:"-" +func GenerateIgnoreTags(tags []string) string { + buf := GetBuffer() + defer PutBuffer(buf) + + for _, tag := range tags { + buf.WriteString(tag) + buf.WriteString(`:"-" `) + } + + return buf.String() +} diff --git a/strmangle/strmangle_test.go b/strmangle/strmangle_test.go index 5a57177..c3626e2 100644 --- a/strmangle/strmangle_test.go +++ b/strmangle/strmangle_test.go @@ -426,3 +426,41 @@ func TestContainsAny(t *testing.T) { t.Errorf("Should not return true") } } + +func TestGenerateTags(t *testing.T) { + tags := GenerateTags([]string{}, "col_name") + if tags != "" { + t.Errorf("Expected empty string, got %s", tags) + } + + tags = GenerateTags([]string{"xml"}, "col_name") + exp := `xml:"col_name" ` + if tags != exp { + t.Errorf("expected %s, got %s", exp, tags) + } + + tags = GenerateTags([]string{"xml", "db"}, "col_name") + exp = `xml:"col_name" db:"col_name" ` + if tags != exp { + t.Errorf("expected %s, got %s", exp, tags) + } +} + +func TestGenerateIgnoreTags(t *testing.T) { + tags := GenerateIgnoreTags([]string{}) + if tags != "" { + t.Errorf("Expected empty string, got %s", tags) + } + + tags = GenerateIgnoreTags([]string{"xml"}) + exp := `xml:"-" ` + if tags != exp { + t.Errorf("expected %s, got %s", exp, tags) + } + + tags = GenerateIgnoreTags([]string{"xml", "db"}) + exp = `xml:"-" db:"-" ` + if tags != exp { + t.Errorf("expected %s, got %s", exp, tags) + } +} diff --git a/templates.go b/templates.go index e5edd55..e760711 100644 --- a/templates.go +++ b/templates.go @@ -20,6 +20,7 @@ type templateData struct { PkgName string NoHooks bool NoAutoTimestamps bool + Tags []string StringFuncs map[string]func(string) string } @@ -124,11 +125,13 @@ var templateFunctions = template.FuncMap{ "camelCase": strmangle.CamelCase, // String Slice ops - "join": func(sep string, slice []string) string { return strings.Join(slice, sep) }, - "joinSlices": strmangle.JoinSlices, - "stringMap": strmangle.StringMap, - "prefixStringSlice": strmangle.PrefixStringSlice, - "containsAny": strmangle.ContainsAny, + "join": func(sep string, slice []string) string { return strings.Join(slice, sep) }, + "joinSlices": strmangle.JoinSlices, + "stringMap": strmangle.StringMap, + "prefixStringSlice": strmangle.PrefixStringSlice, + "containsAny": strmangle.ContainsAny, + "generateTags": strmangle.GenerateTags, + "generateIgnoreTags": strmangle.GenerateIgnoreTags, // String Map ops "makeStringMap": strmangle.MakeStringMap, diff --git a/templates/00_struct.tpl b/templates/00_struct.tpl index 756930a..70d1e29 100644 --- a/templates/00_struct.tpl +++ b/templates/00_struct.tpl @@ -2,22 +2,22 @@ {{.Function.Name}} *{{.ForeignTable.NameGo}} {{- end -}} +{{- $dot := . -}} {{- $tableNameSingular := .Table.Name | singular -}} {{- $modelName := $tableNameSingular | titleCase -}} {{- $modelNameCamel := $tableNameSingular | camelCase -}} // {{$modelName}} is an object representing the database table. type {{$modelName}} struct { {{range $column := .Table.Columns -}} - {{titleCase $column.Name}} {{$column.Type}} `boil:"{{$column.Name}}" json:"{{$column.Name}}{{if $column.Nullable}},omitempty{{end}}" toml:"{{$column.Name}}" yaml:"{{$column.Name}}{{if $column.Nullable}},omitempty{{end}}"` + {{titleCase $column.Name}} {{$column.Type}} `{{generateTags $dot.Tags $column.Name}}boil:"{{$column.Name}}" json:"{{$column.Name}}{{if $column.Nullable}},omitempty{{end}}" toml:"{{$column.Name}}" yaml:"{{$column.Name}}{{if $column.Nullable}},omitempty{{end}}"` {{end -}} {{- if .Table.IsJoinTable -}} {{- else}} - R *{{$modelNameCamel}}R `boil:"-" json:"-" toml:"-" yaml:"-"` - L {{$modelNameCamel}}L `boil:"-" json:"-" toml:"-" yaml:"-"` + R *{{$modelNameCamel}}R `{{generateIgnoreTags $dot.Tags}}boil:"-" json:"-" toml:"-" yaml:"-"` + L {{$modelNameCamel}}L `{{generateIgnoreTags $dot.Tags}}boil:"-" json:"-" toml:"-" yaml:"-"` {{end -}} } -{{- $dot := . -}} {{- if .Table.IsJoinTable -}} {{- else}} // {{$modelNameCamel}}R is where relationships are stored.