fix #81 refactory struct field parser to make fieldas correct in all cases

This commit is contained in:
Huan Du
2022-09-04 00:09:09 +08:00
parent 16a3a33952
commit e6e16111d3
4 changed files with 334 additions and 229 deletions
+2 -2
View File
@@ -134,8 +134,8 @@ We can define a struct type and use field tags to let `Struct` know how to creat
type ATable struct {
Field1 string // If a field doesn't has a tag, use "Field1" as column name in SQL.
Field2 int `db:"field2"` // Use "db" in field tag to set column name used in SQL.
Field3 int64 `db:"field3" fieldtag:"foo,bar"` // Set fieldtag to a field. We can use methods like `Struct#SelectForTag` to use it.
Field4 int64 `db:"field4" fieldtag:"foo"` // If we use `s.SelectForTag(table, "foo")`, columnes of SELECT are field3 and field4.
Field3 int64 `db:"field3" fieldtag:"foo,bar"` // Set fieldtag to a field. We can call `WithTag` to change default tag or call methods like `Struct#SelectForTag` to set tag explicitly.
Field4 int64 `db:"field4" fieldtag:"foo"` // If we use `s.WithTag("foo").Select(table)`, columnes of SELECT are field3 and field4.
Field5 string `db:"field5" fieldas:"f5_alias"` // Use "fieldas" in field tag to set a column alias (AS) used in SELECT.
Ignored int32 `db:"-"` // If we set field name as "-", Struct will ignore it.
unexported int // Unexported field is not visible to Struct.
+104 -167
View File
@@ -8,7 +8,6 @@ import (
"math"
"reflect"
"regexp"
"strings"
)
var (
@@ -46,6 +45,7 @@ type Struct struct {
structType reflect.Type
structFieldsParser structFieldsParser
structTag string
}
var emptyStruct Struct
@@ -88,54 +88,51 @@ func (s *Struct) WithFieldMapper(mapper FieldMapperFunc) *Struct {
return &c
}
// WithTag sets default tag for all builder methods.
// For instance, calling s.WithTag("tag").SelectFrom("t") is the same as calling s.SelectFromForTag("t", "tag").
func (s *Struct) WithTag(tag string) *Struct {
c := *s
c.structTag = tag
return &c
}
// SelectFrom creates a new `SelectBuilder` with table name.
// By default, all exported fields of the s are listed as columns in SELECT.
//
// Caller is responsible to set WHERE condition to find right record.
func (s *Struct) SelectFrom(table string) *SelectBuilder {
return s.SelectFromForTag(table, "")
return s.SelectFromForTag(table, s.structTag)
}
// SelectFromForTag creates a new `SelectBuilder` with table name for a specified tag.
// By default, all fields of the s tagged with tag are listed as columns in SELECT.
//
// Caller is responsible to set WHERE condition to find right record.
func (s *Struct) SelectFromForTag(table string, tag string) *SelectBuilder {
sf := s.structFieldsParser()
sb := s.Flavor.NewSelectBuilder()
func (s *Struct) SelectFromForTag(table string, tag string) (sb *SelectBuilder) {
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
sb = s.Flavor.NewSelectBuilder()
sb.From(table)
if sf.taggedFields == nil {
return sb
}
fields, ok := sf.taggedFields[tag]
if ok {
fields = s.quoteFields(sf, fields)
buf := &bytes.Buffer{}
cols := make([]string, 0, len(fields))
for _, field := range fields {
buf.WriteString(table)
buf.WriteRune('.')
if fieldas, exists := sf.fieldAs[field]; exists {
buf.WriteString(sb.As(field, fieldas))
} else {
buf.WriteString(field)
}
cols = append(cols, buf.String())
buf.Reset()
}
sb.Select(cols...)
} else {
if tagged == nil {
sb.Select("*")
return
}
buf := &bytes.Buffer{}
cols := make([]string, 0, len(tagged.ForRead))
for _, sf := range tagged.ForRead {
buf.WriteString(table)
buf.WriteRune('.')
buf.WriteString(sf.NameForSelect(s.Flavor))
cols = append(cols, buf.String())
buf.Reset()
}
sb.Select(cols...)
return sb
}
@@ -145,7 +142,7 @@ func (s *Struct) SelectFromForTag(table string, tag string) *SelectBuilder {
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
return s.UpdateForTag(table, "", value)
return s.UpdateForTag(table, s.structTag, value)
}
// UpdateForTag creates a new `UpdateBuilder` with table name.
@@ -154,17 +151,13 @@ func (s *Struct) Update(table string, value interface{}) *UpdateBuilder {
//
// Caller is responsible to set WHERE condition to match right record.
func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *UpdateBuilder {
sf := s.structFieldsParser()
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
ub := s.Flavor.NewUpdateBuilder()
ub.Update(table)
if sf.taggedFields == nil {
return ub
}
fields, ok := sf.taggedFields[tag]
if !ok {
if tagged == nil {
return ub
}
@@ -175,25 +168,22 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
return ub
}
quoted := s.quoteFields(sf, fields)
assignments := make([]string, 0, len(fields))
assignments := make([]string, 0, len(tagged.ForWrite))
for i, f := range fields {
name := sf.fieldAlias[f]
for _, sf := range tagged.ForWrite {
name := sf.Name
val := v.FieldByName(name)
if isEmptyValue(val) {
if omitEmptyTagMap, ok := sf.omitEmptyFields[f]; ok {
if omitEmptyTagMap.containsAny("", tag) {
continue
}
if sf.ShouldOmitEmpty("", tag) {
continue
}
} else {
val = dereferencedValue(val)
}
data := val.Interface()
assignments = append(assignments, ub.Assign(quoted[i], data))
assignments = append(assignments, ub.Assign(sf.Quote(s.Flavor), data))
}
ub.Set(assignments...)
@@ -208,7 +198,7 @@ func (s *Struct) UpdateForTag(table string, tag string, value interface{}) *Upda
// If the type of any item in value is not expected, it will be ignored.
// If value is an empty slice, `InsertBuilder#Values` will not be called.
func (s *Struct) InsertInto(table string, value ...interface{}) *InsertBuilder {
return s.InsertIntoForTag(table, "", value...)
return s.InsertIntoForTag(table, s.structTag, value...)
}
// InsertIgnoreInto creates a new `InsertBuilder` with table name using verb INSERT IGNORE INTO.
@@ -219,7 +209,7 @@ func (s *Struct) InsertInto(table string, value ...interface{}) *InsertBuilder {
// If the type of any item in value is not expected, it will be ignored.
// If value is an empty slice, `InsertBuilder#Values` will not be called.
func (s *Struct) InsertIgnoreInto(table string, value ...interface{}) *InsertBuilder {
return s.InsertIgnoreIntoForTag(table, "", value...)
return s.InsertIgnoreIntoForTag(table, s.structTag, value...)
}
// ReplaceInto creates a new `InsertBuilder` with table name using verb REPLACE INTO.
@@ -230,21 +220,16 @@ func (s *Struct) InsertIgnoreInto(table string, value ...interface{}) *InsertBui
// If the type of any item in value is not expected, it will be ignored.
// If value is an empty slice, `InsertBuilder#Values` will not be called.
func (s *Struct) ReplaceInto(table string, value ...interface{}) *InsertBuilder {
return s.ReplaceIntoForTag(table, "", value...)
return s.ReplaceIntoForTag(table, s.structTag, value...)
}
// buildColsAndValuesForTag uses ib to set exported fields tagged with tag as columns
// and add value as a list of values.
func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value ...interface{}) {
sf := s.structFieldsParser()
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
if sf.taggedFields == nil {
return
}
fields, ok := sf.taggedFields[tag]
if !ok {
if tagged == nil {
return
}
@@ -263,26 +248,21 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
return
}
cols := make([]string, 0, len(fields))
cols := make([]string, 0, len(tagged.ForWrite))
values := make([][]interface{}, len(vs))
nilCols := make([]int, len(fields))
nilCols := make([]int, 0, len(tagged.ForWrite))
for idx, f := range fields {
cols = append(cols, f)
name := sf.fieldAlias[f]
shouldOmitempty := false
if omitEmptyTagMap, ok := sf.omitEmptyFields[f]; ok {
if omitEmptyTagMap.containsAny("", tag) {
shouldOmitempty = true
}
}
for _, sf := range tagged.ForWrite {
cols = append(cols, sf.Quote(s.Flavor))
name := sf.Name
shouldOmitEmpty := sf.ShouldOmitEmpty("", tag)
nilCnt := 0
for i, v := range vs {
val := v.FieldByName(name)
if isEmptyValue(val) && shouldOmitempty {
nilCols[idx]++
if isEmptyValue(val) && shouldOmitEmpty {
nilCnt++
}
val = dereferencedValue(val)
@@ -293,6 +273,8 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
values[i] = append(values[i], nil)
}
}
nilCols = append(nilCols, nilCnt)
}
// Try to filter out nil values if possible.
@@ -312,7 +294,6 @@ func (s *Struct) buildColsAndValuesForTag(ib *InsertBuilder, tag string, value .
}
}
filteredCols = s.quoteFields(sf, filteredCols)
ib.Cols(filteredCols...)
for _, value := range filteredValues {
@@ -377,7 +358,7 @@ func (s *Struct) DeleteFrom(table string) *DeleteBuilder {
// Addr takes address of all exported fields of the s from the st.
// The returned result can be used in `Row#Scan` directly.
func (s *Struct) Addr(st interface{}) []interface{} {
return s.AddrForTag("", st)
return s.AddrForTag(s.structTag, st)
}
// AddrForTag takes address of all fields of the s tagged with tag from the st.
@@ -385,20 +366,36 @@ func (s *Struct) Addr(st interface{}) []interface{} {
//
// If tag is not defined in s in advance, returns nil.
func (s *Struct) AddrForTag(tag string, st interface{}) []interface{} {
sf := s.structFieldsParser()
fields, ok := sf.taggedFields[tag]
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
if !ok {
if tagged == nil {
return nil
}
return s.AddrWithCols(fields, st)
return s.addrWithFields(tagged.ForRead, st)
}
// AddrWithCols takes address of all columns defined in cols from the st.
// The returned value can be used in `Row#Scan` directly.
func (s *Struct) AddrWithCols(cols []string, st interface{}) []interface{} {
sf := s.structFieldsParser()
sfs := s.structFieldsParser()
tagged := sfs.Tag(s.structTag)
if tagged == nil {
return nil
}
fields := tagged.Cols(cols)
if fields == nil {
return nil
}
return s.addrWithFields(fields, st)
}
func (s *Struct) addrWithFields(fields []*structField, st interface{}) []interface{} {
v := reflect.ValueOf(st)
v = dereferencedValue(v)
@@ -406,16 +403,10 @@ func (s *Struct) AddrWithCols(cols []string, st interface{}) []interface{} {
return nil
}
for _, c := range cols {
if _, ok := sf.fieldAlias[c]; !ok {
return nil
}
}
addrs := make([]interface{}, 0, len(fields))
addrs := make([]interface{}, 0, len(cols))
for _, c := range cols {
name := sf.fieldAlias[c]
for _, sf := range fields {
name := sf.Name
data := v.FieldByName(name).Addr().Interface()
addrs = append(addrs, data)
}
@@ -425,30 +416,41 @@ func (s *Struct) AddrWithCols(cols []string, st interface{}) []interface{} {
// Columns returns column names of s for all exported struct fields.
func (s *Struct) Columns() []string {
return s.ColumnsForTag("")
return s.ColumnsForTag(s.structTag)
}
// ColumnsForTag returns column names of the s tagged with tag.
func (s *Struct) ColumnsForTag(tag string) (cols []string) {
sf := s.structFieldsParser()
fields, ok := sf.taggedFields[tag]
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
if !ok {
if tagged == nil {
return
}
cols = append(cols, fields...)
cols = make([]string, 0, len(tagged.ForWrite))
for _, sf := range tagged.ForWrite {
cols = append(cols, sf.Alias)
}
return
}
// Values returns a shadow copy of all exported fields in st.
func (s *Struct) Values(st interface{}) []interface{} {
return s.ValuesForTag("", st)
return s.ValuesForTag(s.structTag, st)
}
// ValuesForTag returns a shadow copy of all fields tagged with tag in st.
func (s *Struct) ValuesForTag(tag string, value interface{}) (values []interface{}) {
sf := s.structFieldsParser()
sfs := s.structFieldsParser()
tagged := sfs.Tag(tag)
if tagged == nil {
return
}
v := reflect.ValueOf(value)
v = dereferencedValue(v)
@@ -456,16 +458,10 @@ func (s *Struct) ValuesForTag(tag string, value interface{}) (values []interface
return
}
cols, ok := sf.taggedFields[tag]
values = make([]interface{}, 0, len(tagged.ForWrite))
if !ok {
return
}
values = make([]interface{}, 0, len(cols))
for _, c := range cols {
name := sf.fieldAlias[c]
for _, sf := range tagged.ForWrite {
name := sf.Name
data := v.FieldByName(name).Interface()
values = append(values, data)
}
@@ -473,65 +469,6 @@ func (s *Struct) ValuesForTag(tag string, value interface{}) (values []interface
return
}
func (s *Struct) quoteFields(sf *structFields, fields []string) []string {
// Try best not to allocate new slice.
if len(sf.quotedFields) == 0 {
return fields
}
needQuote := false
for _, field := range fields {
if _, ok := sf.quotedFields[field]; ok {
needQuote = true
break
}
}
if !needQuote {
return fields
}
quoted := make([]string, 0, len(fields))
for _, field := range fields {
if _, ok := sf.quotedFields[field]; ok {
quoted = append(quoted, s.Flavor.Quote(field))
} else {
quoted = append(quoted, field)
}
}
return quoted
}
func getOptMatchedMap(opt string) (res map[string]string) {
res = map[string]string{}
sm := optRegex.FindStringSubmatch(opt)
for i, name := range optRegex.SubexpNames() {
if name != "" {
res[name] = sm[i]
}
}
return
}
func getTagsFromOptParams(opts string) (tags []string) {
tags = splitTokens(opts)
if len(tags) == 0 {
tags = append(tags, "")
}
return
}
func splitTokens(fieldtag string) (res []string) {
res = strings.Split(fieldtag, ",")
for i, v := range res {
res[i] = strings.TrimSpace(v)
}
return
}
func dereferencedType(t reflect.Type) reflect.Type {
for k := t.Kind(); k == reflect.Ptr || k == reflect.Interface; k = t.Kind() {
t = t.Elem()
+27 -8
View File
@@ -777,19 +777,38 @@ func TestStructFieldMapper(t *testing.T) {
}
type structWithAs struct {
T1 string `db:"t1" fieldas:"f1"`
T2 string `db:"t2" fieldas:""` // Empty fieldas is the same as the tag is not set.
T3 string `fieldas:"f3"` // AS works without db tag.
T4 string `db:"t4" fieldas:"f3"` // fieldas tag can duplicate.
T1 string `db:"t1" fieldas:"f1" fieldtag:"tag"`
T2 string `db:"t2" fieldas:""` // Empty fieldas is the same as the tag is not set.
T3 string `db:"t2" fieldas:"f3"` // AS works without db tag.
T4 string `db:"t4" fieldas:"f3" fieldtag:"tag"` // It's OK to set the same fieldas in different tags.
}
func TestStructFieldAs(t *testing.T) {
a := assert.New(t)
s := NewStruct(new(structWithAs))
sb := s.SelectFrom("t")
b := Build(`COPY ($?) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`, sb)
sql, _ := b.Build()
a.Equal(sql, `COPY (SELECT t.t1 AS f1, t.t2, t.T3 AS f3, t.t4 AS f3 FROM t) TO '/path/to/file.csv' (FORMAT CSV, HEADER)`)
value := &structWithAs{
T1: "t1",
T2: "t2",
T3: "t3",
T4: "t4",
}
build := func(builder Builder) string {
sql, _ := builder.Build()
return sql
}
// Struct field T3 is not shadowed by T2.
// Struct field T4 is shadowed by T3 due to same fieldas.
sql := build(s.SelectFrom("t"))
a.Equal(sql, `SELECT t.t1 AS f1, t.t2, t.t2 AS f3 FROM t`)
// Struct field T4 is visible in the tag.
sql = build(s.WithTag("tag").SelectFrom("t"))
a.Equal(sql, `SELECT t.t1 AS f1, t.t4 AS f3 FROM t`)
// Struct field T3 is shadowed by T2 due to same alias.
sql = build(s.Update("t", value))
a.Equal(sql, `UPDATE t SET t1 = ?, t2 = ?, t4 = ?`)
}
func SomeOtherMapper(string) string {
+201 -52
View File
@@ -1,16 +1,35 @@
package sqlbuilder
import (
"fmt"
"reflect"
"strings"
"sync"
)
type structFields struct {
fieldAlias map[string]string
fieldAs map[string]string
taggedFields map[string][]string
quotedFields map[string]struct{}
omitEmptyFields map[string]omitEmptyTagMap
noTag *structTaggedFields
tagged map[string]*structTaggedFields
}
type structTaggedFields struct {
// All columns for SELECT.
ForRead []*structField
colsForRead map[string]*structField
// All columns which can be used in INSERT and UPDATE.
ForWrite []*structField
colsForWrite map[string]struct{}
}
type structField struct {
Name string
Alias string
As string
Tags []string
IsQuoted bool
omitEmptyTags omitEmptyTagMap
}
type structFieldsParser func() *structFields
@@ -25,12 +44,9 @@ func makeCustomFieldsParser(t reflect.Type, mapper FieldMapperFunc) structFields
func makeFieldsParser(t reflect.Type, mapper FieldMapperFunc, useDefault bool) structFieldsParser {
var once sync.Once
sf := &structFields{
fieldAlias: map[string]string{},
fieldAs: map[string]string{},
taggedFields: map[string][]string{},
quotedFields: map[string]struct{}{},
omitEmptyFields: map[string]omitEmptyTagMap{},
sfs := &structFields{
noTag: makeStructTaggedFields(),
tagged: map[string]*structTaggedFields{},
}
return func() *structFields {
@@ -39,14 +55,14 @@ func makeFieldsParser(t reflect.Type, mapper FieldMapperFunc, useDefault bool) s
mapper = DefaultFieldMapper
}
sf.parse(t, mapper, "")
sfs.parse(t, mapper, "")
})
return sf
return sfs
}
}
func (sf *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix string) {
func (sfs *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix string) {
l := t.NumField()
var anonymous []reflect.StructField
@@ -70,43 +86,26 @@ func (sf *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix str
// Parse DBTag.
dbtag := field.Tag.Get(DBTag)
alias := dbtag
if dbtag == "-" {
continue
}
alias := dbtag
if dbtag == "" {
if mapper == nil {
alias = field.Name
} else {
alias = mapper(field.Name)
alias = field.Name
if mapper != nil {
alias = mapper(alias)
}
}
// The alias name has been used by another field.
// This field is shadowed.
if _, ok := sf.fieldAlias[alias]; ok {
continue
}
sf.fieldAlias[alias] = field.Name
// Parse FieldTag.
fieldtag := field.Tag.Get(FieldTag)
tags := splitTokens(fieldtag)
for _, t := range tags {
if t != "" {
sf.taggedFields[t] = append(sf.taggedFields[t], alias)
}
}
sf.taggedFields[""] = append(sf.taggedFields[""], alias)
// Parse FieldOpt.
fieldopt := field.Tag.Get(FieldOpt)
opts := optRegex.FindAllString(fieldopt, -1)
isQuoted := false
omitEmptyTags := omitEmptyTagMap{}
for _, opt := range opts {
optMap := getOptMatchedMap(opt)
@@ -114,45 +113,195 @@ func (sf *structFields) parse(t reflect.Type, mapper FieldMapperFunc, prefix str
switch optMap[optName] {
case fieldOptOmitEmpty:
tags := getTagsFromOptParams(optMap[optParams])
sf.appendOmitEmptyFieldsTags(alias, tags...)
for _, tag := range tags {
omitEmptyTags[tag] = struct{}{}
}
case fieldOptWithQuote:
sf.quotedFields[alias] = struct{}{}
isQuoted = true
}
}
// Parse FieldAs.
fieldas := field.Tag.Get(FieldAs)
if fieldas != "" {
sf.fieldAs[alias] = fieldas
// Parse FieldTag.
fieldtag := field.Tag.Get(FieldTag)
tags := splitTags(fieldtag)
// Make struct field.
structField := &structField{
Name: field.Name,
Alias: alias,
As: fieldas,
Tags: tags,
IsQuoted: isQuoted,
omitEmptyTags: omitEmptyTags,
}
// Make sure all fields can be added to noTag without conflict.
sfs.noTag.Add(structField)
for _, tag := range tags {
sfs.taggedFields(tag).Add(structField)
}
}
for _, field := range anonymous {
ft := dereferencedType(field.Type)
sf.parse(ft, mapper, prefix+field.Name+".")
sfs.parse(ft, mapper, prefix+field.Name+".")
}
}
func (sf *structFields) appendOmitEmptyFieldsTags(alias string, tags ...string) {
if sf.omitEmptyFields[alias] == nil {
sf.omitEmptyFields[alias] = omitEmptyTagMap{}
// Tag returns the fields with tag.
func (sfs *structFields) Tag(tag string) *structTaggedFields {
if tag == "" {
return sfs.noTag
}
for _, tag := range tags {
sf.omitEmptyFields[alias][tag] = struct{}{}
return sfs.tagged[tag]
}
func (sfs *structFields) taggedFields(tag string) *structTaggedFields {
fields, ok := sfs.tagged[tag]
if !ok {
fields = makeStructTaggedFields()
sfs.tagged[tag] = fields
}
return fields
}
func makeStructTaggedFields() *structTaggedFields {
return &structTaggedFields{
colsForRead: map[string]*structField{},
colsForWrite: map[string]struct{}{},
}
}
type omitEmptyTagMap map[string]struct{}
// Add a new field to stfs.
// If field's key exists in stfs.fields, the field is ignored.
func (stfs *structTaggedFields) Add(field *structField) {
key := field.Key()
if _, ok := stfs.colsForRead[key]; !ok {
stfs.colsForRead[key] = field
stfs.ForRead = append(stfs.ForRead, field)
}
key = field.Alias
if _, ok := stfs.colsForWrite[key]; !ok {
stfs.colsForWrite[key] = struct{}{}
stfs.ForWrite = append(stfs.ForWrite, field)
}
}
// Cols returns the fields whose key is one of cols.
// If any column in cols doesn't exist in sfs.fields, returns nil.
func (stfs *structTaggedFields) Cols(cols []string) []*structField {
fields := make([]*structField, 0, len(cols))
for _, col := range cols {
field := stfs.colsForRead[col]
if field == nil {
return nil
}
fields = append(fields, field)
}
return fields
}
// Key returns the key name to identify a field.
func (sf *structField) Key() string {
if sf.As != "" {
return sf.As
}
if sf.Alias != "" {
return sf.Alias
}
return sf.Name
}
// NameForSelect returns the name for SELECT.
func (sf *structField) NameForSelect(flavor Flavor) string {
if sf.As == "" {
return sf.Quote(flavor)
}
return fmt.Sprintf("%s AS %s", sf.Quote(flavor), sf.As)
}
// Quote the Alias in sf with flavor.
func (sf *structField) Quote(flavor Flavor) string {
if !sf.IsQuoted {
return sf.Alias
}
return flavor.Quote(sf.Alias)
}
// ShouldOmitEmpty returns true only if any one of tags is in the omitted tags map.
func (sf *structField) ShouldOmitEmpty(tags ...string) (ret bool) {
omit := sf.omitEmptyTags
if len(omit) == 0 {
return
}
func (sm omitEmptyTagMap) containsAny(tags ...string) (res bool) {
for _, tag := range tags {
if _, res = sm[tag]; res {
if _, ret = omit[tag]; ret {
return
}
}
return
}
type omitEmptyTagMap map[string]struct{}
func getOptMatchedMap(opt string) (res map[string]string) {
res = map[string]string{}
sm := optRegex.FindStringSubmatch(opt)
for i, name := range optRegex.SubexpNames() {
if name != "" {
res[name] = sm[i]
}
}
return
}
func getTagsFromOptParams(opts string) (tags []string) {
tags = splitTags(opts)
if len(tags) == 0 {
tags = append(tags, "")
}
return
}
func splitTags(fieldtag string) (tags []string) {
parts := strings.Split(fieldtag, ",")
for _, v := range parts {
tag := strings.TrimSpace(v)
if tag == "" {
continue
}
tags = append(tags, tag)
}
return
}