open-nomad/jobspec2/hclutil/blockattrs.go
Seth Hoenig b3ea68948b build: run gofmt on all go source files
Go 1.19 will forecefully format all your doc strings. To get this
out of the way, here is one big commit with all the changes gofmt
wants to make.
2022-08-16 11:14:11 -05:00

254 lines
5.2 KiB
Go

package hclutil
import (
"github.com/hashicorp/hcl/v2"
hcls "github.com/hashicorp/hcl/v2/hclsyntax"
)
// BlocksAsAttrs rewrites the hcl.Body so that hcl blocks are treated as
// attributes when schema is unknown.
//
// This conversion is necessary for parsing task driver configs, as they can be
// arbitrary nested without pre-defined schema.
//
// More concretely, it changes the following:
//
// ```
//
// config {
// meta { ... }
// }
//
// ```
//
// to
//
// ```
//
// config {
// meta = { ... } # <- attribute now
// }
//
// ```
func BlocksAsAttrs(body hcl.Body) hcl.Body {
if hclb, ok := body.(*hcls.Body); ok {
return &blockAttrs{body: hclb}
}
return body
}
type blockAttrs struct {
body hcl.Body
hiddenAttrs map[string]struct{}
hiddenBlocks map[string]struct{}
}
func (b *blockAttrs) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
bc, diags := b.body.Content(schema)
bc.Blocks = expandBlocks(bc.Blocks)
return bc, diags
}
func (b *blockAttrs) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
bc, remainBody, diags := b.body.PartialContent(schema)
bc.Blocks = expandBlocks(bc.Blocks)
remain := &blockAttrs{
body: remainBody,
hiddenAttrs: map[string]struct{}{},
hiddenBlocks: map[string]struct{}{},
}
for name := range b.hiddenAttrs {
remain.hiddenAttrs[name] = struct{}{}
}
for typeName := range b.hiddenBlocks {
remain.hiddenBlocks[typeName] = struct{}{}
}
for _, attrS := range schema.Attributes {
remain.hiddenAttrs[attrS.Name] = struct{}{}
}
for _, blockS := range schema.Blocks {
remain.hiddenBlocks[blockS.Type] = struct{}{}
}
return bc, remain, diags
}
func (b *blockAttrs) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
body, ok := b.body.(*hcls.Body)
if !ok {
return b.body.JustAttributes()
}
attrs := make(hcl.Attributes)
var diags hcl.Diagnostics
if body.Attributes == nil && len(body.Blocks) == 0 {
return attrs, diags
}
for name, attr := range body.Attributes {
if _, hidden := b.hiddenAttrs[name]; hidden {
continue
}
na := attr.AsHCLAttribute()
na.Expr = attrExpr(attr.Expr)
attrs[name] = na
}
for _, blocks := range blocksByType(body.Blocks) {
if _, hidden := b.hiddenBlocks[blocks[0].Type]; hidden {
continue
}
b := blocks[0]
attr := &hcls.Attribute{
Name: b.Type,
NameRange: b.TypeRange,
EqualsRange: b.OpenBraceRange,
SrcRange: b.Body.SrcRange,
Expr: blocksToExpr(blocks),
}
attrs[blocks[0].Type] = attr.AsHCLAttribute()
}
return attrs, diags
}
func (b *blockAttrs) MissingItemRange() hcl.Range {
return b.body.MissingItemRange()
}
func expandBlocks(blocks hcl.Blocks) hcl.Blocks {
if len(blocks) == 0 {
return blocks
}
r := make([]*hcl.Block, len(blocks))
for i, b := range blocks {
nb := *b
nb.Body = BlocksAsAttrs(b.Body)
r[i] = &nb
}
return r
}
func blocksByType(blocks hcls.Blocks) map[string]hcls.Blocks {
r := map[string]hcls.Blocks{}
for _, b := range blocks {
r[b.Type] = append(r[b.Type], b)
}
return r
}
func blocksToExpr(blocks hcls.Blocks) hcls.Expression {
if len(blocks) == 0 {
panic("unexpected empty blocks")
}
exprs := make([]hcls.Expression, len(blocks))
for i, b := range blocks {
exprs[i] = blockToExpr(b)
}
last := blocks[len(blocks)-1]
return &hcls.TupleConsExpr{
Exprs: exprs,
SrcRange: hcl.RangeBetween(blocks[0].OpenBraceRange, last.CloseBraceRange),
OpenRange: blocks[0].OpenBraceRange,
}
}
func blockToExpr(b *hcls.Block) hcls.Expression {
items := []hcls.ObjectConsItem{}
for _, attr := range b.Body.Attributes {
keyExpr := &hcls.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: attr.Name,
SrcRange: attr.NameRange,
},
},
SrcRange: attr.NameRange,
}
key := &hcls.ObjectConsKeyExpr{
Wrapped: keyExpr,
}
items = append(items, hcls.ObjectConsItem{
KeyExpr: key,
ValueExpr: attrExpr(attr.Expr),
})
}
for _, blocks := range blocksByType(b.Body.Blocks) {
keyExpr := &hcls.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: blocks[0].Type,
SrcRange: blocks[0].TypeRange,
},
},
SrcRange: blocks[0].TypeRange,
}
key := &hcls.ObjectConsKeyExpr{
Wrapped: keyExpr,
}
item := hcls.ObjectConsItem{
KeyExpr: key,
ValueExpr: blocksToExpr(blocks),
}
items = append(items, item)
}
v := &hcls.ObjectConsExpr{
Items: items,
}
// Create nested maps, with the labels as keys.
// Starts wrapping from most inner label to outer
for i := len(b.Labels) - 1; i >= 0; i-- {
keyExpr := &hcls.ScopeTraversalExpr{
Traversal: hcl.Traversal{
hcl.TraverseRoot{
Name: b.Labels[i],
SrcRange: b.LabelRanges[i],
},
},
SrcRange: b.LabelRanges[i],
}
key := &hcls.ObjectConsKeyExpr{
Wrapped: keyExpr,
}
item := hcls.ObjectConsItem{
KeyExpr: key,
ValueExpr: &hcls.TupleConsExpr{
Exprs: []hcls.Expression{v},
},
}
v = &hcls.ObjectConsExpr{
Items: []hcls.ObjectConsItem{item},
}
}
return v
}
func attrExpr(expr hcls.Expression) hcls.Expression {
if _, ok := expr.(*hcls.ObjectConsExpr); ok {
return &hcls.TupleConsExpr{
Exprs: []hcls.Expression{expr},
SrcRange: expr.Range(),
OpenRange: expr.StartRange(),
}
}
return expr
}