From f27b374e8757d175a537912fd35935cb431794e8 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 21 Dec 2023 17:36:07 +0800 Subject: [PATCH 1/4] define StructTagCases to support multiple case style - also make the template easier to extend fields - refactor template functions - define tag case style constants --- boilingcore/boilingcore.go | 1 + boilingcore/config.go | 28 +++++++++++++++-- boilingcore/templates.go | 53 +++++++++++++++++++++++++++------ main.go | 35 +++++++++++++++++----- templates/main/00_struct.go.tpl | 35 +++++++++++++++------- 5 files changed, 123 insertions(+), 29 deletions(-) diff --git a/boilingcore/boilingcore.go b/boilingcore/boilingcore.go index 86325f548..8afa911c0 100644 --- a/boilingcore/boilingcore.go +++ b/boilingcore/boilingcore.go @@ -149,6 +149,7 @@ func (s *State) Run() error { NoBackReferencing: s.Config.NoBackReferencing, AlwaysWrapErrors: s.Config.AlwaysWrapErrors, StructTagCasing: s.Config.StructTagCasing, + StructTagCases: s.Config.StructTagCases, TagIgnore: make(map[string]struct{}), Tags: s.Config.Tags, RelationTag: s.Config.RelationTag, diff --git a/boilingcore/config.go b/boilingcore/config.go index 54f78b4b4..e83b2b115 100644 --- a/boilingcore/config.go +++ b/boilingcore/config.go @@ -14,6 +14,14 @@ import ( "github.com/volatiletech/sqlboiler/v4/importers" ) +type TagCase string + +const ( + TagCaseCamel TagCase = "camel" + TagCaseSnake TagCase = "snake" + TagCaseTitle TagCase = "title" +) + // Config for the running of the commands type Config struct { DriverName string `toml:"driver_name,omitempty" json:"driver_name,omitempty"` @@ -39,9 +47,16 @@ type Config struct { NoBackReferencing bool `toml:"no_back_reference,omitempty" json:"no_back_reference,omitempty"` AlwaysWrapErrors bool `toml:"always_wrap_errors,omitempty" json:"always_wrap_errors,omitempty"` Wipe bool `toml:"wipe,omitempty" json:"wipe,omitempty"` - StructTagCasing string `toml:"struct_tag_casing,omitempty" json:"struct_tag_casing,omitempty"` - RelationTag string `toml:"relation_tag,omitempty" json:"relation_tag,omitempty"` - TagIgnore []string `toml:"tag_ignore,omitempty" json:"tag_ignore,omitempty"` + + StructTagCases StructTagCases `toml:"struct_tag_cases,omitempty" json:"struct_tag_cases,omitempty"` + + // StructTagCasing is a legacy config field, which will be migrated to StructTagCases in the future. + // When struct-tag-casing is defined, it will be converted to StructTagCases + // Deprecated: use StructTagCases instead. + StructTagCasing string `toml:"struct_tag_casing,omitempty" json:"struct_tag_casing,omitempty"` + + RelationTag string `toml:"relation_tag,omitempty" json:"relation_tag,omitempty"` + TagIgnore []string `toml:"tag_ignore,omitempty" json:"tag_ignore,omitempty"` Imports importers.Collection `toml:"imports,omitempty" json:"imports,omitempty"` @@ -63,6 +78,13 @@ type AutoColumns struct { Deleted string `toml:"deleted,omitempty" json:"deleted,omitempty"` } +type StructTagCases struct { + Json TagCase `toml:"json,omitempty" json:"json,omitempty"` + Yaml TagCase `toml:"yaml,omitempty" json:"yaml,omitempty"` + Toml TagCase `toml:"toml,omitempty" json:"toml,omitempty"` + Boil TagCase `toml:"boil,omitempty" json:"boil,omitempty"` +} + // TypeReplace replaces a column type with something else type TypeReplace struct { Tables []string `toml:"tables,omitempty" json:"tables,omitempty"` diff --git a/boilingcore/templates.go b/boilingcore/templates.go index 958c59e91..2f37ecebf 100644 --- a/boilingcore/templates.go +++ b/boilingcore/templates.go @@ -14,8 +14,9 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/friendsofgo/errors" - "github.com/volatiletech/sqlboiler/v4/drivers" "github.com/volatiletech/strmangle" + + "github.com/volatiletech/sqlboiler/v4/drivers" ) // templateData for sqlboiler templates @@ -58,8 +59,12 @@ type templateData struct { RelationTag string // Generate struct tags as camelCase or snake_case + // Deprecated: use StructTagCases instead. StructTagCasing string + // Generate struct tags as camelCase or snake_case + StructTagCases StructTagCases + // Contains field names that should have tags values set to '-' TagIgnore map[string]struct{} @@ -133,7 +138,9 @@ func (t templateList) Templates() []string { return ret } -func loadTemplates(lazyTemplates []lazyTemplate, testTemplates bool, customFuncs template.FuncMap) (*templateList, error) { +func loadTemplates( + lazyTemplates []lazyTemplate, testTemplates bool, customFuncs template.FuncMap, +) (*templateList, error) { tpl := template.New("") for _, t := range lazyTemplates { @@ -286,13 +293,14 @@ var templateFunctions = template.FuncMap{ "ignore": strmangle.Ignore, // 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, - "generateTags": strmangle.GenerateTags, - "generateIgnoreTags": strmangle.GenerateIgnoreTags, + "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, + "generateTagWithCase": generateTagWithCase, + "generateIgnoreTags": strmangle.GenerateIgnoreTags, // Enum ops "parseEnumName": strmangle.ParseEnumName, @@ -333,3 +341,30 @@ var templateFunctions = template.FuncMap{ "columnDBTypes": drivers.ColumnDBTypes, "getTable": drivers.GetTable, } + +func generateTagWithCase(tagName, tagValue string, c TagCase, nullable bool) string { + buf := strmangle.GetBuffer() + defer strmangle.PutBuffer(buf) + + buf.WriteString(tagName) + buf.WriteString(`:"`) + switch c { + case TagCaseSnake: + // we use snake case by default, so we can simply render the value to the buffer + buf.WriteString(tagValue) + case TagCaseTitle: + buf.WriteString(strmangle.TitleCase(tagValue)) + case TagCaseCamel: + buf.WriteString(strmangle.CamelCase(tagValue)) + default: + buf.WriteString(tagValue) + } + + if nullable { + buf.WriteString(",omitempty") + } + + buf.WriteString(`" `) + + return buf.String() +} diff --git a/main.go b/main.go index 4f4840b8a..009765ac3 100644 --- a/main.go +++ b/main.go @@ -172,13 +172,20 @@ func preRun(cmd *cobra.Command, args []string) error { AlwaysWrapErrors: viper.GetBool("always-wrap-errors"), Wipe: viper.GetBool("wipe"), StructTagCasing: strings.ToLower(viper.GetString("struct-tag-casing")), // camel | snake | title - TagIgnore: viper.GetStringSlice("tag-ignore"), - RelationTag: viper.GetString("relation-tag"), - TemplateDirs: viper.GetStringSlice("templates"), - Tags: viper.GetStringSlice("tag"), - Replacements: viper.GetStringSlice("replace"), - Aliases: boilingcore.ConvertAliases(viper.Get("aliases")), - TypeReplaces: boilingcore.ConvertTypeReplace(viper.Get("types")), + StructTagCases: boilingcore.StructTagCases{ + // make this compatible with the legacy struct-tag-casing config + Json: withDefaultCase(viper.GetString("struct-tag-cases.json"), viper.GetString("struct-tag-casing")), + Yaml: withDefaultCase(viper.GetString("struct-tag-cases.yaml"), viper.GetString("struct-tag-casing")), + Toml: withDefaultCase(viper.GetString("struct-tag-cases.toml"), viper.GetString("struct-tag-casing")), + Boil: withDefaultCase(viper.GetString("struct-tag-cases.boil"), viper.GetString("struct-tag-casing")), + }, + TagIgnore: viper.GetStringSlice("tag-ignore"), + RelationTag: viper.GetString("relation-tag"), + TemplateDirs: viper.GetStringSlice("templates"), + Tags: viper.GetStringSlice("tag"), + Replacements: viper.GetStringSlice("replace"), + Aliases: boilingcore.ConvertAliases(viper.Get("aliases")), + TypeReplaces: boilingcore.ConvertTypeReplace(viper.Get("types")), AutoColumns: boilingcore.AutoColumns{ Created: viper.GetString("auto-columns.created"), Updated: viper.GetString("auto-columns.updated"), @@ -293,3 +300,17 @@ func allKeys(prefix string) []string { } return keySlice } + +func withDefaultCase(configCase string, defaultCases ...string) boilingcore.TagCase { + if len(configCase) > 0 { + return boilingcore.TagCase(strings.ToLower(configCase)) + } + + for _, c := range defaultCases { + if len(c) > 0 { + return boilingcore.TagCase(strings.ToLower(c)) + } + } + + return boilingcore.TagCaseSnake +} diff --git a/templates/main/00_struct.go.tpl b/templates/main/00_struct.go.tpl index 9b76d8e02..8c6648974 100644 --- a/templates/main/00_struct.go.tpl +++ b/templates/main/00_struct.go.tpl @@ -8,18 +8,33 @@ type {{$alias.UpSingular}} struct { {{- $orig_col_name := $column.Name -}} {{- range $column.Comment | splitLines -}} // {{ . }} {{end -}} + {{if ignore $orig_tbl_name $orig_col_name $.TagIgnore -}} {{$colAlias}} {{$column.Type}} `{{generateIgnoreTags $.Tags}}boil:"{{$column.Name}}" json:"-" toml:"-" yaml:"-"` - {{else if eq $.StructTagCasing "title" -}} - {{$colAlias}} {{$column.Type}} `{{generateTags $.Tags $column.Name}}boil:"{{$column.Name}}" json:"{{$column.Name | titleCase}}{{if $column.Nullable}},omitempty{{end}}" toml:"{{$column.Name | titleCase}}" yaml:"{{$column.Name | titleCase}}{{if $column.Nullable}},omitempty{{end}}"` - {{else if eq $.StructTagCasing "camel" -}} - {{$colAlias}} {{$column.Type}} `{{generateTags $.Tags $column.Name}}boil:"{{$column.Name}}" json:"{{$column.Name | camelCase}}{{if $column.Nullable}},omitempty{{end}}" toml:"{{$column.Name | camelCase}}" yaml:"{{$column.Name | camelCase}}{{if $column.Nullable}},omitempty{{end}}"` - {{else if eq $.StructTagCasing "alias" -}} - {{$colAlias}} {{$column.Type}} `{{generateTags $.Tags $colAlias}}boil:"{{$column.Name}}" json:"{{$colAlias}}{{if $column.Nullable}},omitempty{{end}}" toml:"{{$colAlias}}" yaml:"{{$colAlias}}{{if $column.Nullable}},omitempty{{end}}"` - {{else -}} - {{$colAlias}} {{$column.Type}} `{{generateTags $.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 -}} - {{end -}} + {{- else -}} + + {{- /* render column alias and column type */ -}} + {{ $colAlias }} {{ $column.Type -}} + + {{- /* handle struct tags */ -}} + ` + {{- if eq $.StructTagCasing "alias" -}} + {{- generateTags $.Tags $colAlias -}} + {{- generateTagWithCase "json" $colAlias "default" $column.Nullable -}} + {{- generateTagWithCase "yaml" $colAlias "default" $column.Nullable -}} + {{- generateTagWithCase "toml" $colAlias "default" $column.Nullable -}} + {{- generateTagWithCase "boil" $colAlias "default" $column.Nullable -}} + {{- else -}} + {{- generateTags $.Tags $column.Name }} + {{- generateTagWithCase "json" $column.Name $.StructTagCases.Json $column.Nullable -}} + {{- generateTagWithCase "yaml" $column.Name $.StructTagCases.Yaml $column.Nullable -}} + {{- generateTagWithCase "toml" $column.Name $.StructTagCases.Toml $column.Nullable -}} + {{- generateTagWithCase "boil" $column.Name $.StructTagCases.Boil $column.Nullable -}} + {{- end -}} + ` + {{ end -}} + {{ end -}} + {{- if or .Table.IsJoinTable .Table.IsView -}} {{- else}} R *{{$alias.DownSingular}}R `{{generateTags $.Tags $.RelationTag}}boil:"{{$.RelationTag}}" json:"{{$.RelationTag}}" toml:"{{$.RelationTag}}" yaml:"{{$.RelationTag}}"` From 1d0c6564b2d24a6ec600f457c75205462becc27d Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 21 Dec 2023 17:43:28 +0800 Subject: [PATCH 2/4] let generateTagWithCase handle alias --- boilingcore/config.go | 1 + boilingcore/templates.go | 4 +++- templates/main/00_struct.go.tpl | 23 ++++++++++++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/boilingcore/config.go b/boilingcore/config.go index e83b2b115..5635d878f 100644 --- a/boilingcore/config.go +++ b/boilingcore/config.go @@ -20,6 +20,7 @@ const ( TagCaseCamel TagCase = "camel" TagCaseSnake TagCase = "snake" TagCaseTitle TagCase = "title" + TagCaseAlias TagCase = "alias" ) // Config for the running of the commands diff --git a/boilingcore/templates.go b/boilingcore/templates.go index 2f37ecebf..4dd26bb80 100644 --- a/boilingcore/templates.go +++ b/boilingcore/templates.go @@ -342,7 +342,7 @@ var templateFunctions = template.FuncMap{ "getTable": drivers.GetTable, } -func generateTagWithCase(tagName, tagValue string, c TagCase, nullable bool) string { +func generateTagWithCase(tagName, tagValue, alias string, c TagCase, nullable bool) string { buf := strmangle.GetBuffer() defer strmangle.PutBuffer(buf) @@ -356,6 +356,8 @@ func generateTagWithCase(tagName, tagValue string, c TagCase, nullable bool) str buf.WriteString(strmangle.TitleCase(tagValue)) case TagCaseCamel: buf.WriteString(strmangle.CamelCase(tagValue)) + case TagCaseAlias: + buf.WriteString(alias) default: buf.WriteString(tagValue) } diff --git a/templates/main/00_struct.go.tpl b/templates/main/00_struct.go.tpl index 8c6648974..f09e30cb9 100644 --- a/templates/main/00_struct.go.tpl +++ b/templates/main/00_struct.go.tpl @@ -16,20 +16,25 @@ type {{$alias.UpSingular}} struct { {{- /* render column alias and column type */ -}} {{ $colAlias }} {{ $column.Type -}} - {{- /* handle struct tags */ -}} + {{- /* + handle struct tags + StructTagCasing will be replaced with $.StructTagCases + however we need to keep this backward compatible + $.StructTagCasing will only be used when it's set to "alias" + */ -}} ` {{- if eq $.StructTagCasing "alias" -}} {{- generateTags $.Tags $colAlias -}} - {{- generateTagWithCase "json" $colAlias "default" $column.Nullable -}} - {{- generateTagWithCase "yaml" $colAlias "default" $column.Nullable -}} - {{- generateTagWithCase "toml" $colAlias "default" $column.Nullable -}} - {{- generateTagWithCase "boil" $colAlias "default" $column.Nullable -}} + {{- generateTagWithCase "json" $column.Name $colAlias "alias" $column.Nullable -}} + {{- generateTagWithCase "yaml" $column.Name $colAlias "alias" $column.Nullable -}} + {{- generateTagWithCase "toml" $column.Name $colAlias "alias" $column.Nullable -}} + {{- generateTagWithCase "boil" $column.Name $colAlias "alias" $column.Nullable -}} {{- else -}} {{- generateTags $.Tags $column.Name }} - {{- generateTagWithCase "json" $column.Name $.StructTagCases.Json $column.Nullable -}} - {{- generateTagWithCase "yaml" $column.Name $.StructTagCases.Yaml $column.Nullable -}} - {{- generateTagWithCase "toml" $column.Name $.StructTagCases.Toml $column.Nullable -}} - {{- generateTagWithCase "boil" $column.Name $.StructTagCases.Boil $column.Nullable -}} + {{- generateTagWithCase "json" $column.Name $colAlias $.StructTagCases.Json $column.Nullable -}} + {{- generateTagWithCase "yaml" $column.Name $colAlias $.StructTagCases.Yaml $column.Nullable -}} + {{- generateTagWithCase "toml" $column.Name $colAlias $.StructTagCases.Toml $column.Nullable -}} + {{- generateTagWithCase "boil" $column.Name $colAlias $.StructTagCases.Boil $column.Nullable -}} {{- end -}} ` {{ end -}} From 8ca9693ecb9736e4c329e539f6c5a1665a206e91 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 21 Dec 2023 17:55:04 +0800 Subject: [PATCH 3/4] update readme for the struct-tag-cases usage --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index d9507a216..e91227ce9 100644 --- a/README.md +++ b/README.md @@ -644,6 +644,28 @@ down_singular = "teamName" foreign = "Videos" ``` + +##### Custom Struct Tag Case + +Sometimes you might want to customize the case style for different purpose, for example, use camel case for json format and use snake case for yaml, +You may create a section named `[struct-tag-cases]` to define these custom case for each different format: + +```toml +[struct-tag-cases] +toml = "snake" +yaml = "camel" +json = "camel" +boil = "alias" +``` + +By default, the snake case will be used, so you can just setup only few formats: + +```toml +[struct-tag-cases] +json = "camel" +``` + + ##### Foreign Keys You can add foreign keys not defined in the database to your models using the following configuration: From a76f2fcde6f0155896a4ef1c97a357ae4bad39ed Mon Sep 17 00:00:00 2001 From: c9s Date: Sat, 30 Dec 2023 17:06:20 +0800 Subject: [PATCH 4/4] fix tag compatibility --- templates/main/00_struct.go.tpl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/main/00_struct.go.tpl b/templates/main/00_struct.go.tpl index f09e30cb9..5320430fe 100644 --- a/templates/main/00_struct.go.tpl +++ b/templates/main/00_struct.go.tpl @@ -25,16 +25,16 @@ type {{$alias.UpSingular}} struct { ` {{- if eq $.StructTagCasing "alias" -}} {{- generateTags $.Tags $colAlias -}} + {{- generateTagWithCase "boil" $column.Name $colAlias "alias" false -}} {{- generateTagWithCase "json" $column.Name $colAlias "alias" $column.Nullable -}} - {{- generateTagWithCase "yaml" $column.Name $colAlias "alias" $column.Nullable -}} - {{- generateTagWithCase "toml" $column.Name $colAlias "alias" $column.Nullable -}} - {{- generateTagWithCase "boil" $column.Name $colAlias "alias" $column.Nullable -}} + {{- generateTagWithCase "toml" $column.Name $colAlias "alias" false -}} + {{- trim (generateTagWithCase "yaml" $column.Name $colAlias "alias" $column.Nullable) -}} {{- else -}} {{- generateTags $.Tags $column.Name }} + {{- generateTagWithCase "boil" $column.Name $colAlias $.StructTagCases.Boil false -}} {{- generateTagWithCase "json" $column.Name $colAlias $.StructTagCases.Json $column.Nullable -}} - {{- generateTagWithCase "yaml" $column.Name $colAlias $.StructTagCases.Yaml $column.Nullable -}} - {{- generateTagWithCase "toml" $column.Name $colAlias $.StructTagCases.Toml $column.Nullable -}} - {{- generateTagWithCase "boil" $column.Name $colAlias $.StructTagCases.Boil $column.Nullable -}} + {{- generateTagWithCase "toml" $column.Name $colAlias $.StructTagCases.Toml false -}} + {{- trim (generateTagWithCase "yaml" $column.Name $colAlias $.StructTagCases.Yaml $column.Nullable) -}} {{- end -}} ` {{ end -}}