package builder
import (
"bytes"
"fmt"
"io"
"reflect"
"strings"
"github.com/grafana/agent/pkg/river/internal/reflectutil"
"github.com/grafana/agent/pkg/river/internal/rivertags"
"github.com/grafana/agent/pkg/river/token"
)
type Expr struct {
rawTokens []Token
}
func NewExpr() *Expr { return &Expr{} }
func (e *Expr) Tokens() []Token { return e.rawTokens }
func (e *Expr) SetValue(goValue interface{}) {
e.rawTokens = tokenEncode(goValue)
}
func (e *Expr) WriteTo(w io.Writer) (int64, error) {
n, err := printExprTokens(w, e.Tokens())
return int64(n), err
}
func (e *Expr) Bytes() []byte {
var buf bytes.Buffer
_, _ = e.WriteTo(&buf)
return buf.Bytes()
}
type File struct {
body *Body
}
func NewFile() *File { return &File{body: newBody()} }
func (f *File) Tokens() []Token { return f.Body().Tokens() }
func (f *File) Body() *Body { return f.body }
func (f *File) WriteTo(w io.Writer) (int64, error) {
n, err := printFileTokens(w, f.Tokens())
return int64(n), err
}
func (f *File) Bytes() []byte {
var buf bytes.Buffer
_, _ = f.WriteTo(&buf)
return buf.Bytes()
}
type Body struct {
nodes []tokenNode
}
type tokenNode interface {
Tokens() []Token
}
func newBody() *Body {
return &Body{}
}
func (b *Body) Tokens() []Token {
var rawToks []Token
for i, node := range b.nodes {
rawToks = append(rawToks, node.Tokens()...)
if i+1 < len(b.nodes) {
rawToks = append(rawToks, Token{
Tok: token.LITERAL,
Lit: "\n",
})
}
}
return rawToks
}
func (b *Body) AppendTokens(tokens []Token) {
b.nodes = append(b.nodes, tokensSlice(tokens))
}
func (b *Body) AppendBlock(block *Block) {
b.nodes = append(b.nodes, block)
}
func (b *Body) AppendFrom(goValue interface{}) {
if goValue == nil {
return
}
rv := reflect.ValueOf(goValue)
b.encodeFields(rv)
}
func getBlockLabel(rv reflect.Value) string {
tags := rivertags.Get(rv.Type())
for _, tag := range tags {
if tag.Flags&rivertags.FlagLabel != 0 {
return reflectutil.Get(rv, tag).String()
}
}
return ""
}
func (b *Body) encodeFields(rv reflect.Value) {
for rv.Kind() == reflect.Pointer {
if rv.IsNil() {
return
}
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
panic(fmt.Sprintf("river/token/builder: can only encode struct values to bodies, got %s", rv.Type()))
}
fields := rivertags.Get(rv.Type())
for _, field := range fields {
fieldVal := reflectutil.Get(rv, field)
b.encodeField(nil, field, fieldVal)
}
}
func (b *Body) encodeField(prefix []string, field rivertags.Field, fieldValue reflect.Value) {
fieldName := strings.Join(field.Name, ".")
for fieldValue.Kind() == reflect.Pointer {
if fieldValue.IsNil() {
break
}
fieldValue = fieldValue.Elem()
}
if field.Flags&rivertags.FlagOptional != 0 && fieldValue.IsZero() {
return
}
switch {
case field.IsAttr():
b.SetAttributeValue(fieldName, fieldValue.Interface())
case field.IsBlock():
fullName := mergeStringSlice(prefix, field.Name)
switch {
case fieldValue.IsZero():
inner := NewBlock(fullName, "")
b.AppendBlock(inner)
case fieldValue.Kind() == reflect.Slice, fieldValue.Kind() == reflect.Array:
for i := 0; i < fieldValue.Len(); i++ {
elem := fieldValue.Index(i)
b.encodeField(prefix, field, elem)
}
case fieldValue.Kind() == reflect.Struct:
inner := NewBlock(fullName, getBlockLabel(fieldValue))
inner.Body().encodeFields(fieldValue)
b.AppendBlock(inner)
}
case field.IsEnum():
newPrefix := mergeStringSlice(prefix, field.Name)
switch {
case fieldValue.Kind() == reflect.Slice, fieldValue.Kind() == reflect.Array:
for i := 0; i < fieldValue.Len(); i++ {
b.encodeEnumElement(newPrefix, fieldValue.Index(i))
}
default:
panic(fmt.Sprintf("river/token/builder: unrecognized enum kind %s", fieldValue.Kind()))
}
}
}
func mergeStringSlice(a, b []string) []string {
if len(a) == 0 {
return b
} else if len(b) == 0 {
return a
}
res := make([]string, 0, len(a)+len(b))
res = append(res, a...)
res = append(res, b...)
return res
}
func (b *Body) encodeEnumElement(prefix []string, enumElement reflect.Value) {
for enumElement.Kind() == reflect.Pointer {
if enumElement.IsNil() {
return
}
enumElement = enumElement.Elem()
}
fields := rivertags.Get(enumElement.Type())
for _, field := range fields {
fieldVal := reflectutil.Get(enumElement, field)
if !fieldVal.IsValid() || fieldVal.IsZero() {
continue
}
b.encodeField(prefix, field, fieldVal)
break
}
}
func (b *Body) SetAttributeTokens(name string, tokens []Token) {
attr := b.getOrCreateAttribute(name)
attr.RawTokens = tokens
}
func (b *Body) getOrCreateAttribute(name string) *attribute {
for _, n := range b.nodes {
if attr, ok := n.(*attribute); ok && attr.Name == name {
return attr
}
}
newAttr := &attribute{Name: name}
b.nodes = append(b.nodes, newAttr)
return newAttr
}
func (b *Body) SetAttributeValue(name string, goValue interface{}) {
attr := b.getOrCreateAttribute(name)
attr.RawTokens = tokenEncode(goValue)
}
type attribute struct {
Name string
RawTokens []Token
}
func (attr *attribute) Tokens() []Token {
var toks []Token
toks = append(toks, Token{Tok: token.IDENT, Lit: attr.Name})
toks = append(toks, Token{Tok: token.ASSIGN})
toks = append(toks, attr.RawTokens...)
return toks
}
type Block struct {
Name []string
Label string
body *Body
}
func NewBlock(name []string, label string) *Block {
return &Block{
Name: name,
Label: label,
body: newBody(),
}
}
func (b *Block) Tokens() []Token {
var toks []Token
for i, frag := range b.Name {
toks = append(toks, Token{Tok: token.IDENT, Lit: frag})
if i+1 < len(b.Name) {
toks = append(toks, Token{Tok: token.DOT})
}
}
toks = append(toks, Token{Tok: token.LITERAL, Lit: " "})
if b.Label != "" {
toks = append(toks, Token{Tok: token.STRING, Lit: fmt.Sprintf("%q", b.Label)})
}
toks = append(toks, Token{Tok: token.LCURLY}, Token{Tok: token.LITERAL, Lit: "\n"})
toks = append(toks, b.body.Tokens()...)
toks = append(toks, Token{Tok: token.LITERAL, Lit: "\n"}, Token{Tok: token.RCURLY})
return toks
}
func (b *Block) Body() *Body { return b.body }
type tokensSlice []Token
func (tn tokensSlice) Tokens() []Token { return []Token(tn) }