-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathinvocation.go
83 lines (72 loc) · 1.99 KB
/
invocation.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
package codegen
import (
"go/types"
"net/url"
"reflect"
"strings"
"github.com/pkg/errors"
)
type Invocation struct {
GenType *types.Named
Args map[string]string
}
func InvocationsForStruct(aStruct *types.Struct) ([]Invocation, error) {
var invocations []Invocation
for i := 0; i < aStruct.NumFields(); i++ {
tag := reflect.StructTag(aStruct.Tag(i))
genTag, ok := tag.Lookup("codegen")
if !ok {
continue
}
field := aStruct.Field(i)
genType, ok := aStruct.Field(i).Type().(*types.Named)
if !ok {
return nil, errors.New("expected named type for field " + field.Name())
}
args, err := parseArgs(genTag)
if err != nil {
return nil, errors.Wrap(err, "parsing codgen tag for args")
}
invocations = append(invocations, Invocation{
GenType: genType,
Args: args,
})
// If the codegen field is itself a struct, then recurse.
if structType, ok := genType.Underlying().(*types.Struct); ok {
nested, err := InvocationsForStruct(structType)
if err != nil {
return nil, errors.Wrap(err, "nested "+genType.Obj().Name())
}
// Pass any args defined by the outer invocation that aren't defined by
// the inner invocation down.
for arg, v := range args {
for _, n := range nested {
if _, inner := n.Args[arg]; !inner {
n.Args[arg] = v
}
}
}
invocations = append(invocations, nested...)
}
}
return invocations, nil
}
func parseArgs(tag string) (map[string]string, error) {
// The format of the tag of `foo=bar,baz=quux` is almost identical to a query
// string so we picky back on this implementation, first replacing the commas
// with '&' to be compatible with the query string.
values, err := url.ParseQuery(strings.ReplaceAll(tag, ",", "&"))
if err != nil {
return nil, err
}
// If an arg is specified more than once, the first occurrence wins.
args := make(map[string]string, len(values))
for arg, vals := range values {
val := ""
if len(vals) > 0 {
val = vals[0]
}
args[arg] = val
}
return args, nil
}