You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
131 lines
2.8 KiB
131 lines
2.8 KiB
3 years ago
|
package yaml
|
||
|
|
||
|
import (
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
|
||
|
"golang.org/x/xerrors"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// StructTagName tag keyword for Marshal/Unmarshal
|
||
|
StructTagName = "yaml"
|
||
|
)
|
||
|
|
||
|
// StructField information for each the field in structure
|
||
|
type StructField struct {
|
||
|
FieldName string
|
||
|
RenderName string
|
||
|
AnchorName string
|
||
|
AliasName string
|
||
|
IsAutoAnchor bool
|
||
|
IsAutoAlias bool
|
||
|
IsOmitEmpty bool
|
||
|
IsFlow bool
|
||
|
IsInline bool
|
||
|
}
|
||
|
|
||
|
func getTag(field reflect.StructField) string {
|
||
|
// If struct tag `yaml` exist, use that. If no `yaml`
|
||
|
// exists, but `json` does, use that and try the best to
|
||
|
// adhere to its rules
|
||
|
tag := field.Tag.Get(StructTagName)
|
||
|
if tag == "" {
|
||
|
tag = field.Tag.Get(`json`)
|
||
|
}
|
||
|
return tag
|
||
|
}
|
||
|
|
||
|
func structField(field reflect.StructField) *StructField {
|
||
|
tag := getTag(field)
|
||
|
fieldName := strings.ToLower(field.Name)
|
||
|
options := strings.Split(tag, ",")
|
||
|
if len(options) > 0 {
|
||
|
if options[0] != "" {
|
||
|
fieldName = options[0]
|
||
|
}
|
||
|
}
|
||
|
structField := &StructField{
|
||
|
FieldName: field.Name,
|
||
|
RenderName: fieldName,
|
||
|
}
|
||
|
if len(options) > 1 {
|
||
|
for _, opt := range options[1:] {
|
||
|
switch {
|
||
|
case opt == "omitempty":
|
||
|
structField.IsOmitEmpty = true
|
||
|
case opt == "flow":
|
||
|
structField.IsFlow = true
|
||
|
case opt == "inline":
|
||
|
structField.IsInline = true
|
||
|
case strings.HasPrefix(opt, "anchor"):
|
||
|
anchor := strings.Split(opt, "=")
|
||
|
if len(anchor) > 1 {
|
||
|
structField.AnchorName = anchor[1]
|
||
|
} else {
|
||
|
structField.IsAutoAnchor = true
|
||
|
}
|
||
|
case strings.HasPrefix(opt, "alias"):
|
||
|
alias := strings.Split(opt, "=")
|
||
|
if len(alias) > 1 {
|
||
|
structField.AliasName = alias[1]
|
||
|
} else {
|
||
|
structField.IsAutoAlias = true
|
||
|
}
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return structField
|
||
|
}
|
||
|
|
||
|
func isIgnoredStructField(field reflect.StructField) bool {
|
||
|
if field.PkgPath != "" && !field.Anonymous {
|
||
|
// private field
|
||
|
return true
|
||
|
}
|
||
|
tag := getTag(field)
|
||
|
if tag == "-" {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
type StructFieldMap map[string]*StructField
|
||
|
|
||
|
func (m StructFieldMap) isIncludedRenderName(name string) bool {
|
||
|
for _, v := range m {
|
||
|
if v.RenderName == name {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (m StructFieldMap) hasMergeProperty() bool {
|
||
|
for _, v := range m {
|
||
|
if v.IsOmitEmpty && v.IsInline && v.IsAutoAlias {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func structFieldMap(structType reflect.Type) (StructFieldMap, error) {
|
||
|
structFieldMap := StructFieldMap{}
|
||
|
renderNameMap := map[string]struct{}{}
|
||
|
for i := 0; i < structType.NumField(); i++ {
|
||
|
field := structType.Field(i)
|
||
|
if isIgnoredStructField(field) {
|
||
|
continue
|
||
|
}
|
||
|
structField := structField(field)
|
||
|
if _, exists := renderNameMap[structField.RenderName]; exists {
|
||
|
return nil, xerrors.Errorf("duplicated struct field name %s", structField.RenderName)
|
||
|
}
|
||
|
structFieldMap[structField.FieldName] = structField
|
||
|
renderNameMap[structField.RenderName] = struct{}{}
|
||
|
}
|
||
|
return structFieldMap, nil
|
||
|
}
|