Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/token/builder/builder.go
4096 views
1
// Package builder exposes an API to create a River configuration file by
2
// constructing a set of tokens.
3
package builder
4
5
import (
6
"bytes"
7
"fmt"
8
"io"
9
"reflect"
10
"strings"
11
12
"github.com/grafana/agent/pkg/river/internal/reflectutil"
13
"github.com/grafana/agent/pkg/river/internal/rivertags"
14
"github.com/grafana/agent/pkg/river/token"
15
)
16
17
// An Expr represents a single River expression.
18
type Expr struct {
19
rawTokens []Token
20
}
21
22
// NewExpr creates a new Expr.
23
func NewExpr() *Expr { return &Expr{} }
24
25
// Tokens returns the Expr as a set of Tokens.
26
func (e *Expr) Tokens() []Token { return e.rawTokens }
27
28
// SetValue sets the Expr to a River value converted from a Go value. The Go
29
// value is encoded using the normal Go to River encoding rules. If any value
30
// reachable from goValue implements Tokenizer, the printed tokens will instead
31
// be retrieved by calling the RiverTokenize method.
32
func (e *Expr) SetValue(goValue interface{}) {
33
e.rawTokens = tokenEncode(goValue)
34
}
35
36
// WriteTo renders and formats the File, writing the contents to w.
37
func (e *Expr) WriteTo(w io.Writer) (int64, error) {
38
n, err := printExprTokens(w, e.Tokens())
39
return int64(n), err
40
}
41
42
// Bytes renders the File to a formatted byte slice.
43
func (e *Expr) Bytes() []byte {
44
var buf bytes.Buffer
45
_, _ = e.WriteTo(&buf)
46
return buf.Bytes()
47
}
48
49
// A File represents a River configuration file.
50
type File struct {
51
body *Body
52
}
53
54
// NewFile creates a new File.
55
func NewFile() *File { return &File{body: newBody()} }
56
57
// Tokens returns the File as a set of Tokens.
58
func (f *File) Tokens() []Token { return f.Body().Tokens() }
59
60
// Body returns the Body contents of the file.
61
func (f *File) Body() *Body { return f.body }
62
63
// WriteTo renders and formats the File, writing the contents to w.
64
func (f *File) WriteTo(w io.Writer) (int64, error) {
65
n, err := printFileTokens(w, f.Tokens())
66
return int64(n), err
67
}
68
69
// Bytes renders the File to a formatted byte slice.
70
func (f *File) Bytes() []byte {
71
var buf bytes.Buffer
72
_, _ = f.WriteTo(&buf)
73
return buf.Bytes()
74
}
75
76
// Body is a list of block and attribute statements. A Body cannot be manually
77
// created, but is retrieved from a File or Block.
78
type Body struct {
79
nodes []tokenNode
80
}
81
82
// A tokenNode is a structural element which can be converted into a set of
83
// Tokens.
84
type tokenNode interface {
85
// Tokens builds the set of Tokens from the node.
86
Tokens() []Token
87
}
88
89
func newBody() *Body {
90
return &Body{}
91
}
92
93
// Tokens returns the File as a set of Tokens.
94
func (b *Body) Tokens() []Token {
95
var rawToks []Token
96
for i, node := range b.nodes {
97
rawToks = append(rawToks, node.Tokens()...)
98
99
if i+1 < len(b.nodes) {
100
// Append a terminator between each statement in the Body.
101
rawToks = append(rawToks, Token{
102
Tok: token.LITERAL,
103
Lit: "\n",
104
})
105
}
106
}
107
return rawToks
108
}
109
110
// AppendTokens appends raw tokens to the Body.
111
func (b *Body) AppendTokens(tokens []Token) {
112
b.nodes = append(b.nodes, tokensSlice(tokens))
113
}
114
115
// AppendBlock adds a new block inside of the Body.
116
func (b *Body) AppendBlock(block *Block) {
117
b.nodes = append(b.nodes, block)
118
}
119
120
// AppendFrom sets attributes and appends blocks defined by goValue into the
121
// Body. If any value reachable from goValue implements Tokenizer, the printed
122
// tokens will instead be retrieved by calling the RiverTokenize method.
123
//
124
// goValue must be a struct or a pointer to a struct that contains River struct
125
// tags.
126
func (b *Body) AppendFrom(goValue interface{}) {
127
if goValue == nil {
128
return
129
}
130
131
rv := reflect.ValueOf(goValue)
132
b.encodeFields(rv)
133
}
134
135
// getBlockLabel returns the label for a given block.
136
func getBlockLabel(rv reflect.Value) string {
137
tags := rivertags.Get(rv.Type())
138
for _, tag := range tags {
139
if tag.Flags&rivertags.FlagLabel != 0 {
140
return reflectutil.Get(rv, tag).String()
141
}
142
}
143
144
return ""
145
}
146
147
func (b *Body) encodeFields(rv reflect.Value) {
148
for rv.Kind() == reflect.Pointer {
149
if rv.IsNil() {
150
return
151
}
152
rv = rv.Elem()
153
}
154
if rv.Kind() != reflect.Struct {
155
panic(fmt.Sprintf("river/token/builder: can only encode struct values to bodies, got %s", rv.Type()))
156
}
157
158
fields := rivertags.Get(rv.Type())
159
160
for _, field := range fields {
161
fieldVal := reflectutil.Get(rv, field)
162
b.encodeField(nil, field, fieldVal)
163
}
164
}
165
166
func (b *Body) encodeField(prefix []string, field rivertags.Field, fieldValue reflect.Value) {
167
fieldName := strings.Join(field.Name, ".")
168
169
for fieldValue.Kind() == reflect.Pointer {
170
if fieldValue.IsNil() {
171
break
172
}
173
fieldValue = fieldValue.Elem()
174
}
175
if field.Flags&rivertags.FlagOptional != 0 && fieldValue.IsZero() {
176
return
177
}
178
179
switch {
180
case field.IsAttr():
181
b.SetAttributeValue(fieldName, fieldValue.Interface())
182
183
case field.IsBlock():
184
fullName := mergeStringSlice(prefix, field.Name)
185
186
switch {
187
case fieldValue.IsZero():
188
// It shouldn't be possible to have a required block which is unset, but
189
// we'll encode something anyway.
190
inner := NewBlock(fullName, "")
191
b.AppendBlock(inner)
192
193
case fieldValue.Kind() == reflect.Slice, fieldValue.Kind() == reflect.Array:
194
for i := 0; i < fieldValue.Len(); i++ {
195
elem := fieldValue.Index(i)
196
197
// Recursively call encodeField for each element in the slice/array.
198
// The recursive call will hit the case below and add a new block for
199
// each field encountered.
200
b.encodeField(prefix, field, elem)
201
}
202
203
case fieldValue.Kind() == reflect.Struct:
204
inner := NewBlock(fullName, getBlockLabel(fieldValue))
205
inner.Body().encodeFields(fieldValue)
206
b.AppendBlock(inner)
207
}
208
209
case field.IsEnum():
210
// Blocks within an enum have a prefix set.
211
newPrefix := mergeStringSlice(prefix, field.Name)
212
213
switch {
214
case fieldValue.Kind() == reflect.Slice, fieldValue.Kind() == reflect.Array:
215
for i := 0; i < fieldValue.Len(); i++ {
216
b.encodeEnumElement(newPrefix, fieldValue.Index(i))
217
}
218
219
default:
220
panic(fmt.Sprintf("river/token/builder: unrecognized enum kind %s", fieldValue.Kind()))
221
}
222
}
223
}
224
225
func mergeStringSlice(a, b []string) []string {
226
if len(a) == 0 {
227
return b
228
} else if len(b) == 0 {
229
return a
230
}
231
232
res := make([]string, 0, len(a)+len(b))
233
res = append(res, a...)
234
res = append(res, b...)
235
return res
236
}
237
238
func (b *Body) encodeEnumElement(prefix []string, enumElement reflect.Value) {
239
for enumElement.Kind() == reflect.Pointer {
240
if enumElement.IsNil() {
241
return
242
}
243
enumElement = enumElement.Elem()
244
}
245
246
fields := rivertags.Get(enumElement.Type())
247
248
// Find the first non-zero field and encode it.
249
for _, field := range fields {
250
fieldVal := reflectutil.Get(enumElement, field)
251
if !fieldVal.IsValid() || fieldVal.IsZero() {
252
continue
253
}
254
255
b.encodeField(prefix, field, fieldVal)
256
break
257
}
258
}
259
260
// SetAttributeTokens sets an attribute to the Body whose value is a set of raw
261
// tokens. If the attribute was previously set, its value tokens are updated.
262
//
263
// Attributes will be written out in the order they were initially created.
264
func (b *Body) SetAttributeTokens(name string, tokens []Token) {
265
attr := b.getOrCreateAttribute(name)
266
attr.RawTokens = tokens
267
}
268
269
func (b *Body) getOrCreateAttribute(name string) *attribute {
270
for _, n := range b.nodes {
271
if attr, ok := n.(*attribute); ok && attr.Name == name {
272
return attr
273
}
274
}
275
276
newAttr := &attribute{Name: name}
277
b.nodes = append(b.nodes, newAttr)
278
return newAttr
279
}
280
281
// SetAttributeValue sets an attribute in the Body whose value is converted
282
// from a Go value to a River value. The Go value is encoded using the normal
283
// Go to River encoding rules. If any value reachable from goValue implements
284
// Tokenizer, the printed tokens will instead be retrieved by calling the
285
// RiverTokenize method.
286
//
287
// If the attribute was previously set, its value tokens are updated.
288
//
289
// Attributes will be written out in the order they were initially crated.
290
func (b *Body) SetAttributeValue(name string, goValue interface{}) {
291
attr := b.getOrCreateAttribute(name)
292
attr.RawTokens = tokenEncode(goValue)
293
}
294
295
type attribute struct {
296
Name string
297
RawTokens []Token
298
}
299
300
func (attr *attribute) Tokens() []Token {
301
var toks []Token
302
303
toks = append(toks, Token{Tok: token.IDENT, Lit: attr.Name})
304
toks = append(toks, Token{Tok: token.ASSIGN})
305
toks = append(toks, attr.RawTokens...)
306
307
return toks
308
}
309
310
// A Block encapsulates a body within a named and labeled River block. Blocks
311
// must be created by calling NewBlock, but its public struct fields may be
312
// safely modified by callers.
313
type Block struct {
314
// Public fields, safe to be changed by callers:
315
316
Name []string
317
Label string
318
319
// Private fields:
320
321
body *Body
322
}
323
324
// NewBlock returns a new Block with the given name and label. The name/label
325
// can be updated later by modifying the Block's public fields.
326
func NewBlock(name []string, label string) *Block {
327
return &Block{
328
Name: name,
329
Label: label,
330
331
body: newBody(),
332
}
333
}
334
335
// Tokens returns the File as a set of Tokens.
336
func (b *Block) Tokens() []Token {
337
var toks []Token
338
339
for i, frag := range b.Name {
340
toks = append(toks, Token{Tok: token.IDENT, Lit: frag})
341
if i+1 < len(b.Name) {
342
toks = append(toks, Token{Tok: token.DOT})
343
}
344
}
345
346
toks = append(toks, Token{Tok: token.LITERAL, Lit: " "})
347
348
if b.Label != "" {
349
toks = append(toks, Token{Tok: token.STRING, Lit: fmt.Sprintf("%q", b.Label)})
350
}
351
352
toks = append(toks, Token{Tok: token.LCURLY}, Token{Tok: token.LITERAL, Lit: "\n"})
353
toks = append(toks, b.body.Tokens()...)
354
toks = append(toks, Token{Tok: token.LITERAL, Lit: "\n"}, Token{Tok: token.RCURLY})
355
356
return toks
357
}
358
359
// Body returns the Body contained within the Block.
360
func (b *Block) Body() *Body { return b.body }
361
362
type tokensSlice []Token
363
364
func (tn tokensSlice) Tokens() []Token { return []Token(tn) }
365
366