Skip to content

Commit f10c023

Browse files
authored
Merge pull request #1336 from c9s/c9s/custom-struct-tag-cases-issue-1321
feature: support different case style for different format
2 parents d684b80 + a76f2fc commit f10c023

File tree

6 files changed

+153
-29
lines changed

6 files changed

+153
-29
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,28 @@ down_singular = "teamName"
644644
foreign = "Videos"
645645
```
646646

647+
648+
##### Custom Struct Tag Case
649+
650+
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,
651+
You may create a section named `[struct-tag-cases]` to define these custom case for each different format:
652+
653+
```toml
654+
[struct-tag-cases]
655+
toml = "snake"
656+
yaml = "camel"
657+
json = "camel"
658+
boil = "alias"
659+
```
660+
661+
By default, the snake case will be used, so you can just setup only few formats:
662+
663+
```toml
664+
[struct-tag-cases]
665+
json = "camel"
666+
```
667+
668+
647669
##### Foreign Keys
648670

649671
You can add foreign keys not defined in the database to your models using the following configuration:

boilingcore/boilingcore.go

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ func (s *State) Run() error {
149149
NoBackReferencing: s.Config.NoBackReferencing,
150150
AlwaysWrapErrors: s.Config.AlwaysWrapErrors,
151151
StructTagCasing: s.Config.StructTagCasing,
152+
StructTagCases: s.Config.StructTagCases,
152153
TagIgnore: make(map[string]struct{}),
153154
Tags: s.Config.Tags,
154155
RelationTag: s.Config.RelationTag,

boilingcore/config.go

+26-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import (
1414
"github.com/volatiletech/sqlboiler/v4/importers"
1515
)
1616

17+
type TagCase string
18+
19+
const (
20+
TagCaseCamel TagCase = "camel"
21+
TagCaseSnake TagCase = "snake"
22+
TagCaseTitle TagCase = "title"
23+
TagCaseAlias TagCase = "alias"
24+
)
25+
1726
// Config for the running of the commands
1827
type Config struct {
1928
DriverName string `toml:"driver_name,omitempty" json:"driver_name,omitempty"`
@@ -39,9 +48,16 @@ type Config struct {
3948
NoBackReferencing bool `toml:"no_back_reference,omitempty" json:"no_back_reference,omitempty"`
4049
AlwaysWrapErrors bool `toml:"always_wrap_errors,omitempty" json:"always_wrap_errors,omitempty"`
4150
Wipe bool `toml:"wipe,omitempty" json:"wipe,omitempty"`
42-
StructTagCasing string `toml:"struct_tag_casing,omitempty" json:"struct_tag_casing,omitempty"`
43-
RelationTag string `toml:"relation_tag,omitempty" json:"relation_tag,omitempty"`
44-
TagIgnore []string `toml:"tag_ignore,omitempty" json:"tag_ignore,omitempty"`
51+
52+
StructTagCases StructTagCases `toml:"struct_tag_cases,omitempty" json:"struct_tag_cases,omitempty"`
53+
54+
// StructTagCasing is a legacy config field, which will be migrated to StructTagCases in the future.
55+
// When struct-tag-casing is defined, it will be converted to StructTagCases
56+
// Deprecated: use StructTagCases instead.
57+
StructTagCasing string `toml:"struct_tag_casing,omitempty" json:"struct_tag_casing,omitempty"`
58+
59+
RelationTag string `toml:"relation_tag,omitempty" json:"relation_tag,omitempty"`
60+
TagIgnore []string `toml:"tag_ignore,omitempty" json:"tag_ignore,omitempty"`
4561

4662
Imports importers.Collection `toml:"imports,omitempty" json:"imports,omitempty"`
4763

@@ -63,6 +79,13 @@ type AutoColumns struct {
6379
Deleted string `toml:"deleted,omitempty" json:"deleted,omitempty"`
6480
}
6581

82+
type StructTagCases struct {
83+
Json TagCase `toml:"json,omitempty" json:"json,omitempty"`
84+
Yaml TagCase `toml:"yaml,omitempty" json:"yaml,omitempty"`
85+
Toml TagCase `toml:"toml,omitempty" json:"toml,omitempty"`
86+
Boil TagCase `toml:"boil,omitempty" json:"boil,omitempty"`
87+
}
88+
6689
// TypeReplace replaces a column type with something else
6790
type TypeReplace struct {
6891
Tables []string `toml:"tables,omitempty" json:"tables,omitempty"`

boilingcore/templates.go

+46-9
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import (
1414

1515
"github.com/Masterminds/sprig/v3"
1616
"github.com/friendsofgo/errors"
17-
"github.com/volatiletech/sqlboiler/v4/drivers"
1817
"github.com/volatiletech/strmangle"
18+
19+
"github.com/volatiletech/sqlboiler/v4/drivers"
1920
)
2021

2122
// templateData for sqlboiler templates
@@ -58,8 +59,12 @@ type templateData struct {
5859
RelationTag string
5960

6061
// Generate struct tags as camelCase or snake_case
62+
// Deprecated: use StructTagCases instead.
6163
StructTagCasing string
6264

65+
// Generate struct tags as camelCase or snake_case
66+
StructTagCases StructTagCases
67+
6368
// Contains field names that should have tags values set to '-'
6469
TagIgnore map[string]struct{}
6570

@@ -133,7 +138,9 @@ func (t templateList) Templates() []string {
133138
return ret
134139
}
135140

136-
func loadTemplates(lazyTemplates []lazyTemplate, testTemplates bool, customFuncs template.FuncMap) (*templateList, error) {
141+
func loadTemplates(
142+
lazyTemplates []lazyTemplate, testTemplates bool, customFuncs template.FuncMap,
143+
) (*templateList, error) {
137144
tpl := template.New("")
138145

139146
for _, t := range lazyTemplates {
@@ -286,13 +293,14 @@ var templateFunctions = template.FuncMap{
286293
"ignore": strmangle.Ignore,
287294

288295
// String Slice ops
289-
"join": func(sep string, slice []string) string { return strings.Join(slice, sep) },
290-
"joinSlices": strmangle.JoinSlices,
291-
"stringMap": strmangle.StringMap,
292-
"prefixStringSlice": strmangle.PrefixStringSlice,
293-
"containsAny": strmangle.ContainsAny,
294-
"generateTags": strmangle.GenerateTags,
295-
"generateIgnoreTags": strmangle.GenerateIgnoreTags,
296+
"join": func(sep string, slice []string) string { return strings.Join(slice, sep) },
297+
"joinSlices": strmangle.JoinSlices,
298+
"stringMap": strmangle.StringMap,
299+
"prefixStringSlice": strmangle.PrefixStringSlice,
300+
"containsAny": strmangle.ContainsAny,
301+
"generateTags": strmangle.GenerateTags,
302+
"generateTagWithCase": generateTagWithCase,
303+
"generateIgnoreTags": strmangle.GenerateIgnoreTags,
296304

297305
// Enum ops
298306
"parseEnumName": strmangle.ParseEnumName,
@@ -333,3 +341,32 @@ var templateFunctions = template.FuncMap{
333341
"columnDBTypes": drivers.ColumnDBTypes,
334342
"getTable": drivers.GetTable,
335343
}
344+
345+
func generateTagWithCase(tagName, tagValue, alias string, c TagCase, nullable bool) string {
346+
buf := strmangle.GetBuffer()
347+
defer strmangle.PutBuffer(buf)
348+
349+
buf.WriteString(tagName)
350+
buf.WriteString(`:"`)
351+
switch c {
352+
case TagCaseSnake:
353+
// we use snake case by default, so we can simply render the value to the buffer
354+
buf.WriteString(tagValue)
355+
case TagCaseTitle:
356+
buf.WriteString(strmangle.TitleCase(tagValue))
357+
case TagCaseCamel:
358+
buf.WriteString(strmangle.CamelCase(tagValue))
359+
case TagCaseAlias:
360+
buf.WriteString(alias)
361+
default:
362+
buf.WriteString(tagValue)
363+
}
364+
365+
if nullable {
366+
buf.WriteString(",omitempty")
367+
}
368+
369+
buf.WriteString(`" `)
370+
371+
return buf.String()
372+
}

main.go

+28-7
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,20 @@ func preRun(cmd *cobra.Command, args []string) error {
172172
AlwaysWrapErrors: viper.GetBool("always-wrap-errors"),
173173
Wipe: viper.GetBool("wipe"),
174174
StructTagCasing: strings.ToLower(viper.GetString("struct-tag-casing")), // camel | snake | title
175-
TagIgnore: viper.GetStringSlice("tag-ignore"),
176-
RelationTag: viper.GetString("relation-tag"),
177-
TemplateDirs: viper.GetStringSlice("templates"),
178-
Tags: viper.GetStringSlice("tag"),
179-
Replacements: viper.GetStringSlice("replace"),
180-
Aliases: boilingcore.ConvertAliases(viper.Get("aliases")),
181-
TypeReplaces: boilingcore.ConvertTypeReplace(viper.Get("types")),
175+
StructTagCases: boilingcore.StructTagCases{
176+
// make this compatible with the legacy struct-tag-casing config
177+
Json: withDefaultCase(viper.GetString("struct-tag-cases.json"), viper.GetString("struct-tag-casing")),
178+
Yaml: withDefaultCase(viper.GetString("struct-tag-cases.yaml"), viper.GetString("struct-tag-casing")),
179+
Toml: withDefaultCase(viper.GetString("struct-tag-cases.toml"), viper.GetString("struct-tag-casing")),
180+
Boil: withDefaultCase(viper.GetString("struct-tag-cases.boil"), viper.GetString("struct-tag-casing")),
181+
},
182+
TagIgnore: viper.GetStringSlice("tag-ignore"),
183+
RelationTag: viper.GetString("relation-tag"),
184+
TemplateDirs: viper.GetStringSlice("templates"),
185+
Tags: viper.GetStringSlice("tag"),
186+
Replacements: viper.GetStringSlice("replace"),
187+
Aliases: boilingcore.ConvertAliases(viper.Get("aliases")),
188+
TypeReplaces: boilingcore.ConvertTypeReplace(viper.Get("types")),
182189
AutoColumns: boilingcore.AutoColumns{
183190
Created: viper.GetString("auto-columns.created"),
184191
Updated: viper.GetString("auto-columns.updated"),
@@ -293,3 +300,17 @@ func allKeys(prefix string) []string {
293300
}
294301
return keySlice
295302
}
303+
304+
func withDefaultCase(configCase string, defaultCases ...string) boilingcore.TagCase {
305+
if len(configCase) > 0 {
306+
return boilingcore.TagCase(strings.ToLower(configCase))
307+
}
308+
309+
for _, c := range defaultCases {
310+
if len(c) > 0 {
311+
return boilingcore.TagCase(strings.ToLower(c))
312+
}
313+
}
314+
315+
return boilingcore.TagCaseSnake
316+
}

templates/main/00_struct.go.tpl

+30-10
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,38 @@ type {{$alias.UpSingular}} struct {
88
{{- $orig_col_name := $column.Name -}}
99
{{- range $column.Comment | splitLines -}} // {{ . }}
1010
{{end -}}
11+
1112
{{if ignore $orig_tbl_name $orig_col_name $.TagIgnore -}}
1213
{{$colAlias}} {{$column.Type}} `{{generateIgnoreTags $.Tags}}boil:"{{$column.Name}}" json:"-" toml:"-" yaml:"-"`
13-
{{else if eq $.StructTagCasing "title" -}}
14-
{{$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}}"`
15-
{{else if eq $.StructTagCasing "camel" -}}
16-
{{$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}}"`
17-
{{else if eq $.StructTagCasing "alias" -}}
18-
{{$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}}"`
19-
{{else -}}
20-
{{$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}}"`
21-
{{end -}}
22-
{{end -}}
14+
{{- else -}}
15+
16+
{{- /* render column alias and column type */ -}}
17+
{{ $colAlias }} {{ $column.Type -}}
18+
19+
{{- /*
20+
handle struct tags
21+
StructTagCasing will be replaced with $.StructTagCases
22+
however we need to keep this backward compatible
23+
$.StructTagCasing will only be used when it's set to "alias"
24+
*/ -}}
25+
`
26+
{{- if eq $.StructTagCasing "alias" -}}
27+
{{- generateTags $.Tags $colAlias -}}
28+
{{- generateTagWithCase "boil" $column.Name $colAlias "alias" false -}}
29+
{{- generateTagWithCase "json" $column.Name $colAlias "alias" $column.Nullable -}}
30+
{{- generateTagWithCase "toml" $column.Name $colAlias "alias" false -}}
31+
{{- trim (generateTagWithCase "yaml" $column.Name $colAlias "alias" $column.Nullable) -}}
32+
{{- else -}}
33+
{{- generateTags $.Tags $column.Name }}
34+
{{- generateTagWithCase "boil" $column.Name $colAlias $.StructTagCases.Boil false -}}
35+
{{- generateTagWithCase "json" $column.Name $colAlias $.StructTagCases.Json $column.Nullable -}}
36+
{{- generateTagWithCase "toml" $column.Name $colAlias $.StructTagCases.Toml false -}}
37+
{{- trim (generateTagWithCase "yaml" $column.Name $colAlias $.StructTagCases.Yaml $column.Nullable) -}}
38+
{{- end -}}
39+
`
40+
{{ end -}}
41+
{{ end -}}
42+
2343
{{- if or .Table.IsJoinTable .Table.IsView -}}
2444
{{- else}}
2545
R *{{$alias.DownSingular}}R `{{generateTags $.Tags $.RelationTag}}boil:"{{$.RelationTag}}" json:"{{$.RelationTag}}" toml:"{{$.RelationTag}}" yaml:"{{$.RelationTag}}"`

0 commit comments

Comments
 (0)