257 lines
5.3 KiB
Go
257 lines
5.3 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
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
|
|
}
|