Skip to content

Commit

Permalink
Merge pull request #1336 from c9s/c9s/custom-struct-tag-cases-issue-1321
Browse files Browse the repository at this point in the history
feature: support different case style for different format
  • Loading branch information
stephenafamo authored Jan 2, 2024
2 parents d684b80 + a76f2fc commit f10c023
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 29 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions boilingcore/boilingcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
29 changes: 26 additions & 3 deletions boilingcore/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ import (
"github.com/volatiletech/sqlboiler/v4/importers"
)

type TagCase string

const (
TagCaseCamel TagCase = "camel"
TagCaseSnake TagCase = "snake"
TagCaseTitle TagCase = "title"
TagCaseAlias TagCase = "alias"
)

// Config for the running of the commands
type Config struct {
DriverName string `toml:"driver_name,omitempty" json:"driver_name,omitempty"`
Expand All @@ -39,9 +48,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"`

Expand All @@ -63,6 +79,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"`
Expand Down
55 changes: 46 additions & 9 deletions boilingcore/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -333,3 +341,32 @@ var templateFunctions = template.FuncMap{
"columnDBTypes": drivers.ColumnDBTypes,
"getTable": drivers.GetTable,
}

func generateTagWithCase(tagName, tagValue, alias 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))
case TagCaseAlias:
buf.WriteString(alias)
default:
buf.WriteString(tagValue)
}

if nullable {
buf.WriteString(",omitempty")
}

buf.WriteString(`" `)

return buf.String()
}
35 changes: 28 additions & 7 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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
}
40 changes: 30 additions & 10 deletions templates/main/00_struct.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,38 @@ 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
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 "boil" $column.Name $colAlias "alias" false -}}
{{- generateTagWithCase "json" $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 "toml" $column.Name $colAlias $.StructTagCases.Toml false -}}
{{- trim (generateTagWithCase "yaml" $column.Name $colAlias $.StructTagCases.Yaml $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}}"`
Expand Down

0 comments on commit f10c023

Please sign in to comment.