-
Notifications
You must be signed in to change notification settings - Fork 18
/
convertmain.go
137 lines (113 loc) · 3.41 KB
/
convertmain.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 larker
import (
"errors"
"fmt"
"github.com/cirruslabs/cirrus-cli/pkg/yamlhelper"
"go.starlark.net/starlark"
"gopkg.in/yaml.v3"
"strings"
)
var (
ErrMalformedKeyValueTuple = errors.New("malformed key-value tuple")
ErrNotADictOrTuple = errors.New("main() should return a list of either dicts or tuples")
ErrMixedList = errors.New("main() should return a list of either dicts or tuples, " +
"found both types in a single list")
)
func convertInstructions(instructions *starlark.List) (*yaml.Node, error) {
if instructions == nil || instructions.Len() == 0 {
return nil, nil
}
iter := instructions.Iterate()
defer iter.Done()
var listValue starlark.Value
var serializableMainResult []*yaml.Node
var usesTuples, usesDicts bool
for iter.Next(&listValue) {
k, v, isTuple, err := listValueToKV(listValue)
if err != nil {
return nil, err
}
if isTuple {
usesTuples = true
} else {
usesDicts = true
}
serializableMainResult = append(serializableMainResult, yamlhelper.NewStringNode(k))
serializableMainResult = append(serializableMainResult, v)
}
if usesTuples && usesDicts {
return nil, ErrMixedList
}
return yamlhelper.NewMapNode(serializableMainResult), nil
}
func listValueToKV(listValue starlark.Value) (string, *yaml.Node, bool, error) {
switch value := listValue.(type) {
case starlark.Tuple:
// YAML configuration freely accepts duplicate map keys, but that is not allowed in Starlark,
// so we support an alternative syntax where the map key and value are decomposed into a tuple:
// [(key, value)]
// ...which additionally allows us to express anything and not just tasks.
if value.Len() != 2 {
return "", nil, true, fmt.Errorf("%w: tuple should contain exactly 2 elements",
ErrMalformedKeyValueTuple)
}
key, ok := value.Index(0).(starlark.String)
if !ok {
return "", nil, true, fmt.Errorf("%w: first tuple element should be a string",
ErrMalformedKeyValueTuple)
}
return key.GoString(), convertValue(value.Index(1)), true, nil
case *starlark.Dict:
return "task", convertValue(value), false, nil
default:
return "", nil, false, ErrNotADictOrTuple
}
}
func convertValue(v starlark.Value) *yaml.Node {
switch value := v.(type) {
case *starlark.List:
return convertList(value)
case *starlark.Dict:
return convertDict(value)
default:
var valueNode yaml.Node
_ = valueNode.Encode(convertPrimitive(value))
return &valueNode
}
}
func convertList(l *starlark.List) *yaml.Node {
iter := l.Iterate()
defer iter.Done()
var listValue starlark.Value
var items []*yaml.Node
for iter.Next(&listValue) {
items = append(items, convertValue(listValue))
}
return yamlhelper.NewSeqNode(items)
}
func convertDict(d *starlark.Dict) *yaml.Node {
var items []*yaml.Node
for _, dictTuple := range d.Items() {
items = append(items, yamlhelper.NewStringNode(strings.Trim(dictTuple[0].String(), "'\"")))
switch value := dictTuple[1].(type) {
case *starlark.List:
items = append(items, convertList(value))
case *starlark.Dict:
items = append(items, convertDict(value))
default:
var valueNode yaml.Node
_ = valueNode.Encode(convertPrimitive(value))
items = append(items, &valueNode)
}
}
return yamlhelper.NewMapNode(items)
}
func convertPrimitive(value starlark.Value) interface{} {
switch typedValue := value.(type) {
case starlark.Int:
res, _ := typedValue.Int64()
return res
default:
return typedValue
}
}