-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathinterpolate.go
168 lines (142 loc) · 4.21 KB
/
interpolate.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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package pipeline
import (
"github.com/buildkite/go-pipeline/ordered"
"github.com/buildkite/interpolate"
)
// This file contains helpers for recursively interpolating all the strings in
// pipeline objects.
// stringTransformer implementations mutate strings.
type stringTransformer interface {
Transform(string) (string, error)
}
// envInterpolator returns a reusable string transform that replaces
// variables (${FOO}) with their values from a map.
type envInterpolator struct {
env interpolate.Env
}
// Transform calls interpolate.Interpolate to transform the string.
func (e envInterpolator) Transform(s string) (string, error) {
return interpolate.Interpolate(e.env, s)
}
// selfInterpolater describes types that can interpolate themselves in-place.
// They can use the string transformer on string fields, or use
// interpolate{Slice,Map,OrderedMap,Any} on their other contents, to do this.
type selfInterpolater interface {
interpolate(stringTransformer) error
}
// interpolateAny interpolates most things, mostly in-place. When passed a
// string, it returns a new string. Anything it doesn't know how to interpolate
// is returned unaltered.
func interpolateAny[T any](tf stringTransformer, o T) (T, error) {
// The box-typeswitch-unbox dance is required because the Go compiler
// has no type switch for type parameters.
var err error
a := any(o)
switch t := a.(type) {
case selfInterpolater:
err = t.interpolate(tf)
case *string:
err = interpolateString(tf, t)
case string:
a, err = tf.Transform(t)
case []any:
err = interpolateSlice(tf, t)
case []string:
err = interpolateSlice(tf, t)
case map[string]any:
err = interpolateMap(tf, t)
case map[string]string:
err = interpolateMap(tf, t)
case *ordered.Map[string, any]:
err = interpolateOrderedMap(tf, t)
case *ordered.Map[string, string]:
err = interpolateOrderedMap(tf, t)
default:
return o, nil
}
// This happens if T is an interface type and o was interface-nil to begin
// with. (You can't type assert interface-nil.)
if a == nil {
var zt T
return zt, err
}
return a.(T), err
}
// interpolateString is a helper to interpolate a string field in-place
// (requiring a pointer to the field).
func interpolateString(tf stringTransformer, p *string) error {
if p == nil {
return nil
}
s, err := tf.Transform(*p)
if err != nil {
return err
}
*p = s
return nil
}
// interpolateSlice applies interpolateAny over any type of slice. Values in the
// slice are updated in-place.
func interpolateSlice[E any, S ~[]E](tf stringTransformer, s S) error {
for i, e := range s {
// It could be a string, so replace the old value with the new.
inte, err := interpolateAny(tf, e)
if err != nil {
return err
}
s[i] = inte
}
return nil
}
// interpolateMapValues applies interpolateAny over the values of any type of
// map. The map is altered in-place.
func interpolateMapValues[K comparable, V any, M ~map[K]V](tf stringTransformer, m M) error {
for k, v := range m {
// V could be string, so be sure to replace the old value with the new.
intv, err := interpolateAny(tf, v)
if err != nil {
return err
}
m[k] = intv
}
return nil
}
// interpolateMap applies interpolateAny over both keys and values of any type
// of map. The map is altered in-place.
func interpolateMap[K comparable, V any, M ~map[K]V](tf stringTransformer, m M) error {
for k, v := range m {
// We interpolate both keys and values.
intk, err := interpolateAny(tf, k)
if err != nil {
return err
}
// V could be string, so be sure to replace the old value with the new.
intv, err := interpolateAny(tf, v)
if err != nil {
return err
}
// If the key changed due to interpolation, delete the old key.
if k != intk {
delete(m, k)
}
m[intk] = intv
}
return nil
}
// interpolateOrderedMap applies interpolateAny over any type of ordered.Map.
// The map is altered in-place.
func interpolateOrderedMap[K comparable, V any](tf stringTransformer, m *ordered.Map[K, V]) error {
return m.Range(func(k K, v V) error {
// We interpolate both keys and values.
intk, err := interpolateAny(tf, k)
if err != nil {
return err
}
intv, err := interpolateAny(tf, v)
if err != nil {
return err
}
m.Replace(k, intk, intv)
return nil
})
}