-
Notifications
You must be signed in to change notification settings - Fork 32
/
reflect.go
137 lines (128 loc) · 3.76 KB
/
reflect.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package form
import (
"html/template"
"reflect"
"strings"
)
// valueOf is basically just reflect.ValueOf, but if the Kind() of the
// value is a pointer or interface it will try to get the reflect.Value
// of the underlying element, and if the pointer is nil it will
// create a new instance of the type and return the reflect.Value of it.
//
// This is used to make the rest of the fields function simpler.
func valueOf(v interface{}) reflect.Value {
rv := reflect.ValueOf(v)
// If a nil pointer is passed in but has a type we can recover, but I
// really should just panic and tell people to fix their shitty code.
if rv.Type().Kind() == reflect.Ptr && rv.IsNil() {
rv = reflect.New(rv.Type().Elem()).Elem()
}
// If we have a pointer or interface let's try to get the underlying
// element
for rv.Kind() == reflect.Ptr || rv.Kind() == reflect.Interface {
rv = rv.Elem()
}
return rv
}
func fields(v interface{}, names ...string) []field {
rv := valueOf(v)
if rv.Kind() != reflect.Struct {
// We can't really do much with a non-struct type. I suppose this
// could eventually support maps as well, but for now it does not.
panic("invalid value; only structs are supported")
}
t := rv.Type()
ret := make([]field, 0, t.NumField())
for i := 0; i < t.NumField(); i++ {
rf := rv.Field(i)
// If this is a nil pointer, create a new instance of the element.
// This could probably be done in a simpler way given that we
// typically recur with this value, but this works so I'm letting it
// be.
if t.Field(i).Type.Kind() == reflect.Ptr && rf.IsNil() {
rf = reflect.New(t.Field(i).Type.Elem()).Elem()
}
// If this is a struct it has nested fields we need to add. The
// simplest way to do this is to recursively call `fields` but
// to provide the name of this struct field to be added as a prefix
// to the fields.
if rf.Kind() == reflect.Struct {
ret = append(ret, fields(rf.Interface(), append(names, t.Field(i).Name)...)...)
continue
}
// If we are still in this loop then we aren't dealing with a nested
// struct and need to add the field. First we check to see if the
// ignore tag is present, then we set default values, then finally
// we overwrite defaults with any provided tags.
tags := parseTags(t.Field(i).Tag.Get("form"))
if _, ok := tags["-"]; ok {
continue
}
name := append(names, t.Field(i).Name)
f := field{
Name: strings.Join(name, "."),
Label: t.Field(i).Name,
Placeholder: t.Field(i).Name,
Type: "text",
Value: rv.Field(i).Interface(),
}
applyTags(&f, tags)
ret = append(ret, f)
}
return ret
}
func applyTags(f *field, tags map[string]string) {
if v, ok := tags["name"]; ok {
f.Name = v
}
if v, ok := tags["label"]; ok {
f.Label = v
// DO NOT move this label check after the placeholder check or
// this will cause issues.
f.Placeholder = v
}
if v, ok := tags["placeholder"]; ok {
f.Placeholder = v
}
if v, ok := tags["type"]; ok {
f.Type = v
}
if v, ok := tags["id"]; ok {
f.ID = v
}
if v, ok := tags["footer"]; ok {
// Probably shouldn't be HTML but whatever.
f.Footer = template.HTML(v)
}
}
func parseTags(tags string) map[string]string {
tags = strings.TrimSpace(tags)
if len(tags) == 0 {
return map[string]string{}
}
split := strings.Split(tags, ";")
ret := make(map[string]string, len(split))
for _, tag := range split {
kv := strings.Split(tag, "=")
if len(kv) < 2 {
if kv[0] == "-" {
return map[string]string{
"-": "this field is ignored",
}
}
continue
}
k, v := strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])
ret[k] = v
}
return ret
}
type field struct {
Name string
Label string
Placeholder string
Type string
ID string
Value interface{}
Footer template.HTML
}