2019-06-17 09:48:05 +00:00
|
|
|
package parser
|
2018-12-04 13:24:04 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
// MetadataOpts Options for the metadata.
|
|
|
|
type MetadataOpts struct {
|
|
|
|
TagName string
|
|
|
|
AllowSliceAsStruct bool
|
|
|
|
}
|
|
|
|
|
2019-06-17 09:48:05 +00:00
|
|
|
// AddMetadata adds metadata such as type, inferred from element, to a node.
|
2019-11-28 20:56:04 +00:00
|
|
|
func AddMetadata(element interface{}, node *Node, opts MetadataOpts) error {
|
|
|
|
return metadata{MetadataOpts: opts}.Add(element, node)
|
|
|
|
}
|
|
|
|
|
|
|
|
type metadata struct {
|
|
|
|
MetadataOpts
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add adds metadata such as type, inferred from element, to a node.
|
|
|
|
func (m metadata) Add(element interface{}, node *Node) error {
|
2019-01-18 14:18:04 +00:00
|
|
|
if node == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-04 13:24:04 +00:00
|
|
|
if len(node.Children) == 0 {
|
|
|
|
return fmt.Errorf("invalid node %s: no child", node.Name)
|
|
|
|
}
|
|
|
|
|
2019-06-17 09:48:05 +00:00
|
|
|
if element == nil {
|
2018-12-04 13:24:04 +00:00
|
|
|
return errors.New("nil structure")
|
|
|
|
}
|
|
|
|
|
2019-06-17 09:48:05 +00:00
|
|
|
rootType := reflect.TypeOf(element)
|
2018-12-04 13:24:04 +00:00
|
|
|
node.Kind = rootType.Kind()
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
return m.browseChildren(rootType, node)
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
func (m metadata) browseChildren(fType reflect.Type, node *Node) error {
|
2019-06-17 09:48:05 +00:00
|
|
|
for _, child := range node.Children {
|
2019-11-28 20:56:04 +00:00
|
|
|
if err := m.add(fType, child); err != nil {
|
2019-06-17 09:48:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
func (m metadata) add(rootType reflect.Type, node *Node) error {
|
2018-12-04 13:24:04 +00:00
|
|
|
rType := rootType
|
|
|
|
if rootType.Kind() == reflect.Ptr {
|
|
|
|
rType = rootType.Elem()
|
|
|
|
}
|
|
|
|
|
2020-07-12 10:56:22 +00:00
|
|
|
if rType.Kind() == reflect.Map && rType.Elem().Kind() == reflect.Interface {
|
|
|
|
addRawValue(node)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
field, err := m.findTypedField(rType, node)
|
2018-12-04 13:24:04 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = isSupportedType(field); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fType := field.Type
|
|
|
|
node.Kind = fType.Kind()
|
2019-06-17 09:48:05 +00:00
|
|
|
node.Tag = field.Tag
|
2018-12-04 13:24:04 +00:00
|
|
|
|
|
|
|
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct ||
|
|
|
|
fType.Kind() == reflect.Map {
|
2019-11-28 20:56:04 +00:00
|
|
|
if len(node.Children) == 0 && field.Tag.Get(m.TagName) != TagLabelAllowEmpty {
|
2019-06-17 09:48:05 +00:00
|
|
|
return fmt.Errorf("%s cannot be a standalone element (type %s)", node.Name, fType)
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
node.Disabled = len(node.Value) > 0 && !strings.EqualFold(node.Value, "true") && field.Tag.Get(m.TagName) == TagLabelAllowEmpty
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(node.Children) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Struct || fType.Kind() == reflect.Ptr && fType.Elem().Kind() == reflect.Struct {
|
2019-11-28 20:56:04 +00:00
|
|
|
return m.browseChildren(fType, node)
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Map {
|
2020-07-12 10:56:22 +00:00
|
|
|
if fType.Elem().Kind() == reflect.Interface {
|
|
|
|
addRawValue(node)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-12-04 13:24:04 +00:00
|
|
|
for _, child := range node.Children {
|
|
|
|
// elem is a map entry value type
|
|
|
|
elem := fType.Elem()
|
|
|
|
child.Kind = elem.Kind()
|
|
|
|
|
|
|
|
if elem.Kind() == reflect.Map || elem.Kind() == reflect.Struct ||
|
|
|
|
(elem.Kind() == reflect.Ptr && elem.Elem().Kind() == reflect.Struct) {
|
2019-11-28 20:56:04 +00:00
|
|
|
if err = m.browseChildren(elem, child); err != nil {
|
2018-12-04 13:24:04 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Slice {
|
2019-11-28 20:56:04 +00:00
|
|
|
if m.AllowSliceAsStruct && field.Tag.Get(TagLabelSliceAsStruct) != "" {
|
|
|
|
return m.browseChildren(fType.Elem(), node)
|
2019-06-17 09:48:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, ch := range node.Children {
|
|
|
|
ch.Kind = fType.Elem().Kind()
|
2019-11-28 20:56:04 +00:00
|
|
|
if err = m.browseChildren(fType.Elem(), ch); err != nil {
|
2019-06-17 09:48:05 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("invalid node %s: %v", node.Name, fType.Kind())
|
|
|
|
}
|
|
|
|
|
2019-11-28 20:56:04 +00:00
|
|
|
func (m metadata) findTypedField(rType reflect.Type, node *Node) (reflect.StructField, error) {
|
2018-12-04 13:24:04 +00:00
|
|
|
for i := 0; i < rType.NumField(); i++ {
|
|
|
|
cField := rType.Field(i)
|
|
|
|
|
|
|
|
fieldName := cField.Tag.Get(TagLabelSliceAsStruct)
|
2019-11-28 20:56:04 +00:00
|
|
|
if !m.AllowSliceAsStruct || len(fieldName) == 0 {
|
2018-12-04 13:24:04 +00:00
|
|
|
fieldName = cField.Name
|
|
|
|
}
|
|
|
|
|
2019-06-17 09:48:05 +00:00
|
|
|
if IsExported(cField) {
|
|
|
|
if cField.Anonymous {
|
|
|
|
if cField.Type.Kind() == reflect.Struct {
|
2019-11-28 20:56:04 +00:00
|
|
|
structField, err := m.findTypedField(cField.Type, node)
|
2019-06-17 09:48:05 +00:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return structField, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if strings.EqualFold(fieldName, node.Name) {
|
|
|
|
node.FieldName = cField.Name
|
|
|
|
return cField, nil
|
|
|
|
}
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflect.StructField{}, fmt.Errorf("field not found, node: %s", node.Name)
|
|
|
|
}
|
|
|
|
|
2019-06-17 09:48:05 +00:00
|
|
|
// IsExported reports whether f is exported.
|
2018-12-04 13:24:04 +00:00
|
|
|
// https://golang.org/pkg/reflect/#StructField
|
2019-06-17 09:48:05 +00:00
|
|
|
func IsExported(f reflect.StructField) bool {
|
|
|
|
return f.PkgPath == ""
|
2018-12-04 13:24:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func isSupportedType(field reflect.StructField) error {
|
|
|
|
fType := field.Type
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Slice {
|
|
|
|
switch fType.Elem().Kind() {
|
|
|
|
case reflect.String,
|
|
|
|
reflect.Bool,
|
|
|
|
reflect.Int,
|
|
|
|
reflect.Int8,
|
|
|
|
reflect.Int16,
|
|
|
|
reflect.Int32,
|
|
|
|
reflect.Int64,
|
|
|
|
reflect.Uint,
|
|
|
|
reflect.Uint8,
|
|
|
|
reflect.Uint16,
|
|
|
|
reflect.Uint32,
|
|
|
|
reflect.Uint64,
|
|
|
|
reflect.Uintptr,
|
|
|
|
reflect.Float32,
|
2019-06-17 09:48:05 +00:00
|
|
|
reflect.Float64,
|
|
|
|
reflect.Struct,
|
|
|
|
reflect.Ptr:
|
2018-12-04 13:24:04 +00:00
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported slice type: %v", fType)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Map && fType.Key().Kind() != reflect.String {
|
|
|
|
return fmt.Errorf("unsupported map key type: %v", fType.Key())
|
|
|
|
}
|
|
|
|
|
|
|
|
if fType.Kind() == reflect.Func {
|
|
|
|
return fmt.Errorf("unsupported type: %v", fType)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-07-12 10:56:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
RawMap section
|
|
|
|
*/
|
|
|
|
|
|
|
|
func addRawValue(node *Node) {
|
|
|
|
if node.RawValue == nil {
|
|
|
|
node.RawValue = nodeToRawMap(node)
|
|
|
|
}
|
|
|
|
|
|
|
|
node.Children = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func nodeToRawMap(node *Node) map[string]interface{} {
|
|
|
|
result := map[string]interface{}{}
|
|
|
|
|
|
|
|
squashNode(node, result, true)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func squashNode(node *Node, acc map[string]interface{}, root bool) {
|
|
|
|
if len(node.Children) == 0 {
|
|
|
|
acc[node.Name] = node.Value
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// slice
|
|
|
|
if isArrayKey(node.Children[0].Name) {
|
|
|
|
var accChild []interface{}
|
|
|
|
|
|
|
|
for _, child := range node.Children {
|
|
|
|
tmp := map[string]interface{}{}
|
|
|
|
squashNode(child, tmp, false)
|
|
|
|
accChild = append(accChild, tmp[child.Name])
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[node.Name] = accChild
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// map
|
|
|
|
var accChild map[string]interface{}
|
|
|
|
if root {
|
|
|
|
accChild = acc
|
|
|
|
} else {
|
|
|
|
accChild = typedRawMap(acc, node.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, child := range node.Children {
|
|
|
|
squashNode(child, accChild, false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func typedRawMap(m map[string]interface{}, k string) map[string]interface{} {
|
|
|
|
if m[k] == nil {
|
|
|
|
m[k] = map[string]interface{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
r, ok := m[k].(map[string]interface{})
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("unsupported value (key: %s): %T", k, m[k]))
|
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func isArrayKey(name string) bool {
|
|
|
|
return name[0] == '[' && name[len(name)-1] == ']'
|
|
|
|
}
|