-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkwargs2go.go
119 lines (102 loc) · 3 KB
/
kwargs2go.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
package startype
import (
"fmt"
"reflect"
"go.starlark.net/starlark"
)
type KwargsValue struct {
kwargs []starlark.Tuple
}
// Kwargs starts the conversion of a Starlark kwargs (keyword args) value
// to a Go struct. The function uses annotated fields on the struct to describe
// the keyword argument mapping as:
//
// var Param struct {
// OutputFile string `name:"output_file" optional:"true"`
// SourcePaths []string `name:"output_path"`
// }
//
// The struct can be mapped to the following keyword args:
//
// kwargs := []starlark.Tuple{
// {starlark.String("output_file"), starlark.String("/tmp/out.tar.gz")},
// {starlark.String("source_paths"), starlark.NewList([]starlark.Value{starlark.String("/tmp/myfile")})},
// }
//
// # Example
//
// Kwargs(kwargs).Go(&Param)
//
// Supported annotation: `name:"arg_name" required:"true|false" (default false)`
func Kwargs(kwargs []starlark.Tuple) *KwargsValue {
return &KwargsValue{kwargs: kwargs}
}
func (v *KwargsValue) Go(gostruct any) error {
if v.kwargs == nil {
return fmt.Errorf("keyword arguments is nil")
}
goval := reflect.ValueOf(gostruct)
gotype := reflect.ValueOf(gostruct).Type()
if gotype.Kind() != reflect.Pointer || goval.IsNil() {
return fmt.Errorf("kwargs expects a non-nil pointer to a struct, got %v", gotype.Kind())
}
return kwargsToGo(v.kwargs, goval.Elem())
}
func kwargsToGo(kwargs []starlark.Tuple, goval reflect.Value) error {
gotype := goval.Type()
if gotype.Kind() != reflect.Struct {
return fmt.Errorf("target type %s: a struct", gotype.Kind())
}
if !goval.IsValid() {
goval.Set(reflect.Zero(goval.Type()))
}
for i := 0; i < goval.NumField(); i++ {
field := gotype.Field(i)
argName, ok := field.Tag.Lookup("name")
if !ok {
continue
}
// get arg from keyword args (use either tag or field name)
kwarg, err := getKwarg(kwargs, argName, field.Name)
if err != nil {
return err
}
// is arg marked required? By default args are required=false
// an arg is required if it is explicitly marked with "true" or "yes"
// otherwise, it's ignored
argRequired, _ := field.Tag.Lookup("required")
switch argRequired {
case "true", "yes":
if kwarg == starlark.None {
return fmt.Errorf("argument '%s' is required", argName)
}
default:
}
// set field value if not None
if kwarg != starlark.None {
fieldVal := goval.FieldByName(field.Name)
if fieldVal.Kind() == reflect.Pointer {
fieldVal.Set(reflect.New(field.Type.Elem()))
fieldVal = fieldVal.Elem()
} else {
fieldVal.Set(reflect.New(field.Type).Elem())
}
if err := starlarkToGo(kwarg, fieldVal); err != nil {
return err
}
}
}
return nil
}
func getKwarg(kwargs []starlark.Tuple, argName, defaultName string) (starlark.Value, error) {
for _, kwarg := range kwargs {
arg, ok := kwarg.Index(0).(starlark.String)
if !ok {
return nil, fmt.Errorf("keyword arg name is not a string")
}
if string(arg) == argName || string(arg) == defaultName {
return kwarg.Index(1), nil
}
}
return starlark.None, nil
}