hcldec: BlockLabelSpec decoding
A BlockLabelSpec can be placed in the nested spec structure of one of the block specs to require and obtain labels on that block. This is a more generic methodology than BlockMapSpec since it allows the result to be a list or set with the labels inside the values, rather than forcing all the label tuples to be unique and losing the ordering by collapsing into a map structure.
This commit is contained in:
parent
a6dff4e9f9
commit
0d6247f4cf
21
hcldec/block_labels.go
Normal file
21
hcldec/block_labels.go
Normal file
@ -0,0 +1,21 @@
|
||||
package hcldec
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
type blockLabel struct {
|
||||
Value string
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
func labelsForBlock(block *hcl.Block) []blockLabel {
|
||||
ret := make([]blockLabel, len(block.Labels))
|
||||
for i := range block.Labels {
|
||||
ret[i] = blockLabel{
|
||||
Value: block.Labels[i],
|
||||
Range: block.LabelRanges[i],
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
@ -5,7 +5,7 @@ import (
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func decode(body hcl.Body, block *hcl.Block, ctx *hcl.EvalContext, spec Spec, partial bool) (cty.Value, hcl.Body, hcl.Diagnostics) {
|
||||
func decode(body hcl.Body, blockLabels []blockLabel, ctx *hcl.EvalContext, spec Spec, partial bool) (cty.Value, hcl.Body, hcl.Diagnostics) {
|
||||
schema := ImpliedSchema(spec)
|
||||
|
||||
var content *hcl.BodyContent
|
||||
@ -18,15 +18,15 @@ func decode(body hcl.Body, block *hcl.Block, ctx *hcl.EvalContext, spec Spec, pa
|
||||
content, diags = body.Content(schema)
|
||||
}
|
||||
|
||||
val, valDiags := spec.decode(content, block, ctx)
|
||||
val, valDiags := spec.decode(content, blockLabels, ctx)
|
||||
diags = append(diags, valDiags...)
|
||||
|
||||
return val, leftovers, diags
|
||||
}
|
||||
|
||||
func sourceRange(body hcl.Body, block *hcl.Block, spec Spec) hcl.Range {
|
||||
func sourceRange(body hcl.Body, blockLabels []blockLabel, spec Spec) hcl.Range {
|
||||
schema := ImpliedSchema(spec)
|
||||
content, _, _ := body.PartialContent(schema)
|
||||
|
||||
return spec.sourceRange(content, block)
|
||||
return spec.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
@ -18,5 +18,6 @@ func init() {
|
||||
gob.Register((*BlockListSpec)(nil))
|
||||
gob.Register((*BlockSetSpec)(nil))
|
||||
gob.Register((*BlockMapSpec)(nil))
|
||||
gob.Register((*BlockLabelSpec)(nil))
|
||||
gob.Register((*DefaultSpec)(nil))
|
||||
}
|
||||
|
@ -148,6 +148,54 @@ b {
|
||||
cty.EmptyObjectVal,
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b "baz" {
|
||||
}
|
||||
`,
|
||||
&BlockSpec{
|
||||
TypeName: "b",
|
||||
Nested: &BlockLabelSpec{
|
||||
Index: 0,
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.StringVal("baz"),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b "baz" {}
|
||||
b "foo" {}
|
||||
`,
|
||||
&BlockSpec{
|
||||
TypeName: "b",
|
||||
Nested: &BlockLabelSpec{
|
||||
Index: 0,
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.StringVal("baz"),
|
||||
1, // duplicate "b" block
|
||||
},
|
||||
{
|
||||
`
|
||||
b {
|
||||
}
|
||||
`,
|
||||
&BlockSpec{
|
||||
TypeName: "b",
|
||||
Nested: &BlockLabelSpec{
|
||||
Index: 0,
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.NullVal(cty.DynamicPseudoType),
|
||||
1, // missing name label
|
||||
},
|
||||
{
|
||||
``,
|
||||
&BlockSpec{
|
||||
@ -218,6 +266,22 @@ b {}
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" {}
|
||||
b "bar" {}
|
||||
`,
|
||||
&BlockListSpec{
|
||||
TypeName: "b",
|
||||
Nested: &BlockLabelSpec{
|
||||
Name: "name",
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b {}
|
||||
b {}
|
||||
b {}
|
||||
@ -261,6 +325,31 @@ b {}
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" "bar" {}
|
||||
b "bar" "baz" {}
|
||||
`,
|
||||
&BlockSetSpec{
|
||||
TypeName: "b",
|
||||
Nested: TupleSpec{
|
||||
&BlockLabelSpec{
|
||||
Name: "name",
|
||||
Index: 1,
|
||||
},
|
||||
&BlockLabelSpec{
|
||||
Name: "type",
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.SetVal([]cty.Value{
|
||||
cty.TupleVal([]cty.Value{cty.StringVal("bar"), cty.StringVal("foo")}),
|
||||
cty.TupleVal([]cty.Value{cty.StringVal("baz"), cty.StringVal("bar")}),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" {}
|
||||
b "bar" {}
|
||||
`,
|
||||
@ -388,6 +477,42 @@ b "foo" "bar" {}
|
||||
cty.MapVal(map[string]cty.Value{"foo": cty.MapVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}),
|
||||
1, // duplicate b block
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" "bar" {}
|
||||
b "bar" "baz" {}
|
||||
`,
|
||||
&BlockMapSpec{
|
||||
TypeName: "b",
|
||||
LabelNames: []string{"type"},
|
||||
Nested: &BlockLabelSpec{
|
||||
Name: "name",
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.MapVal(map[string]cty.Value{
|
||||
"foo": cty.StringVal("bar"),
|
||||
"bar": cty.StringVal("baz"),
|
||||
}),
|
||||
0,
|
||||
},
|
||||
{
|
||||
`
|
||||
b "foo" {}
|
||||
`,
|
||||
&BlockMapSpec{
|
||||
TypeName: "b",
|
||||
LabelNames: []string{"type"},
|
||||
Nested: &BlockLabelSpec{
|
||||
Name: "name",
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
nil,
|
||||
cty.MapValEmpty(cty.DynamicPseudoType),
|
||||
1, // missing name
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
157
hcldec/spec.go
157
hcldec/spec.go
@ -20,7 +20,7 @@ type Spec interface {
|
||||
//
|
||||
// "block" is provided only by the nested calls performed by the spec
|
||||
// types that work on block bodies.
|
||||
decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
|
||||
// Call the given callback once for each of the nested specs that would
|
||||
// get decoded with the same body and block as the receiver. This should
|
||||
@ -31,7 +31,7 @@ type Spec interface {
|
||||
// spec in the given content, in the context of the given block
|
||||
// (which might be null). If the corresponding item is missing, return
|
||||
// a place where it might be inserted.
|
||||
sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range
|
||||
sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range
|
||||
}
|
||||
|
||||
type visitFunc func(spec Spec)
|
||||
@ -62,24 +62,20 @@ func (s ObjectSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s ObjectSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s ObjectSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
vals := make(map[string]cty.Value, len(s))
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for k, spec := range s {
|
||||
var kd hcl.Diagnostics
|
||||
vals[k], kd = spec.decode(content, block, ctx)
|
||||
vals[k], kd = spec.decode(content, blockLabels, ctx)
|
||||
diags = append(diags, kd...)
|
||||
}
|
||||
|
||||
return cty.ObjectVal(vals), diags
|
||||
}
|
||||
|
||||
func (s ObjectSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
if block != nil {
|
||||
return block.DefRange
|
||||
}
|
||||
|
||||
func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// This is not great, but the best we can do. In practice, it's rather
|
||||
// strange to ask for the source range of an entire top-level body, since
|
||||
// that's already readily available to the caller.
|
||||
@ -96,24 +92,20 @@ func (s TupleSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s TupleSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s TupleSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
vals := make([]cty.Value, len(s))
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for i, spec := range s {
|
||||
var ed hcl.Diagnostics
|
||||
vals[i], ed = spec.decode(content, block, ctx)
|
||||
vals[i], ed = spec.decode(content, blockLabels, ctx)
|
||||
diags = append(diags, ed...)
|
||||
}
|
||||
|
||||
return cty.TupleVal(vals), diags
|
||||
}
|
||||
|
||||
func (s TupleSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
if block != nil {
|
||||
return block.DefRange
|
||||
}
|
||||
|
||||
func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// This is not great, but the best we can do. In practice, it's rather
|
||||
// strange to ask for the source range of an entire top-level body, since
|
||||
// that's already readily available to the caller.
|
||||
@ -153,7 +145,7 @@ func (s *AttrSpec) attrSchemata() []hcl.AttributeSchema {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *AttrSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
attr, exists := content.Attributes[s.Name]
|
||||
if !exists {
|
||||
return content.MissingItemRange
|
||||
@ -162,7 +154,7 @@ func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.R
|
||||
return attr.Expr.Range()
|
||||
}
|
||||
|
||||
func (s *AttrSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *AttrSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
attr, exists := content.Attributes[s.Name]
|
||||
if !exists {
|
||||
// We don't need to check required and emit a diagnostic here, because
|
||||
@ -204,11 +196,11 @@ func (s *LiteralSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
// leaf node
|
||||
}
|
||||
|
||||
func (s *LiteralSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *LiteralSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return s.Value, nil
|
||||
}
|
||||
|
||||
func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *LiteralSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// No sensible range to return for a literal, so the caller had better
|
||||
// ensure it doesn't cause any diagnostics.
|
||||
return hcl.Range{
|
||||
@ -231,11 +223,11 @@ func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
return s.Expr.Variables()
|
||||
}
|
||||
|
||||
func (s *ExprSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *ExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return s.Expr.Value(ctx)
|
||||
}
|
||||
|
||||
func (s *ExprSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *ExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
return s.Expr.Range()
|
||||
}
|
||||
|
||||
@ -259,7 +251,8 @@ func (s *BlockSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||
return []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: s.TypeName,
|
||||
Type: s.TypeName,
|
||||
LabelNames: findLabelSpecs(s.Nested),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -283,7 +276,7 @@ func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
return Variables(childBlock.Body, s.Nested)
|
||||
}
|
||||
|
||||
func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *BlockSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
var childBlock *hcl.Block
|
||||
@ -325,12 +318,12 @@ func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.
|
||||
if s.Nested == nil {
|
||||
panic("BlockSpec with no Nested Spec")
|
||||
}
|
||||
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
||||
val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
|
||||
diags = append(diags, childDiags...)
|
||||
return val, diags
|
||||
}
|
||||
|
||||
func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *BlockSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
var childBlock *hcl.Block
|
||||
for _, candidate := range content.Blocks {
|
||||
if candidate.Type != s.TypeName {
|
||||
@ -345,7 +338,7 @@ func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.
|
||||
return content.MissingItemRange
|
||||
}
|
||||
|
||||
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||
}
|
||||
|
||||
// A BlockListSpec is a Spec that produces a cty list of the results of
|
||||
@ -365,10 +358,8 @@ func (s *BlockListSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||
return []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: s.TypeName,
|
||||
// FIXME: Need to peek into s.Nested to see if it has any
|
||||
// BlockLabelSpec instances, which will define then how many
|
||||
// labels we need.
|
||||
Type: s.TypeName,
|
||||
LabelNames: findLabelSpecs(s.Nested),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -388,7 +379,7 @@ func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversa
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *BlockListSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if s.Nested == nil {
|
||||
@ -402,10 +393,10 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
||||
continue
|
||||
}
|
||||
|
||||
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
||||
val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
|
||||
diags = append(diags, childDiags...)
|
||||
elems = append(elems, val)
|
||||
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, childBlock, s.Nested))
|
||||
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
|
||||
}
|
||||
|
||||
if len(elems) < s.MinItems {
|
||||
@ -437,7 +428,7 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We return the source range of the _first_ block of the given type,
|
||||
// since they are not guaranteed to form a contiguous range.
|
||||
|
||||
@ -455,7 +446,7 @@ func (s *BlockListSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block)
|
||||
return content.MissingItemRange
|
||||
}
|
||||
|
||||
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||
}
|
||||
|
||||
// A BlockSetSpec is a Spec that produces a cty set of the results of
|
||||
@ -475,7 +466,8 @@ func (s *BlockSetSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||
return []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: s.TypeName,
|
||||
Type: s.TypeName,
|
||||
LabelNames: findLabelSpecs(s.Nested),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -495,7 +487,7 @@ func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *BlockSetSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if s.Nested == nil {
|
||||
@ -509,10 +501,10 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
|
||||
continue
|
||||
}
|
||||
|
||||
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
||||
val, _, childDiags := decode(childBlock.Body, labelsForBlock(childBlock), ctx, s.Nested, false)
|
||||
diags = append(diags, childDiags...)
|
||||
elems = append(elems, val)
|
||||
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, childBlock, s.Nested))
|
||||
sourceRanges = append(sourceRanges, sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested))
|
||||
}
|
||||
|
||||
if len(elems) < s.MinItems {
|
||||
@ -544,7 +536,7 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We return the source range of the _first_ block of the given type,
|
||||
// since they are not guaranteed to form a contiguous range.
|
||||
|
||||
@ -562,7 +554,7 @@ func (s *BlockSetSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h
|
||||
return content.MissingItemRange
|
||||
}
|
||||
|
||||
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||
}
|
||||
|
||||
// A BlockMapSpec is a Spec that produces a cty map of the results of
|
||||
@ -585,7 +577,7 @@ func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
||||
return []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: s.TypeName,
|
||||
LabelNames: s.LabelNames,
|
||||
LabelNames: append(s.LabelNames, findLabelSpecs(s.Nested)...),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -605,7 +597,7 @@ func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
func (s *BlockMapSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if s.Nested == nil {
|
||||
@ -618,9 +610,10 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
|
||||
continue
|
||||
}
|
||||
|
||||
val, _, childDiags := decode(childBlock.Body, childBlock, ctx, s.Nested, false)
|
||||
childLabels := labelsForBlock(childBlock)
|
||||
val, _, childDiags := decode(childBlock.Body, childLabels[len(s.LabelNames):], ctx, s.Nested, false)
|
||||
targetMap := elems
|
||||
for _, key := range childBlock.Labels[:len(childBlock.LabelRanges)-1] {
|
||||
for _, key := range childBlock.Labels[:len(s.LabelNames)-1] {
|
||||
if _, exists := targetMap[key]; !exists {
|
||||
targetMap[key] = make(map[string]interface{})
|
||||
}
|
||||
@ -629,7 +622,7 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
|
||||
|
||||
diags = append(diags, childDiags...)
|
||||
|
||||
key := childBlock.Labels[len(childBlock.Labels)-1]
|
||||
key := childBlock.Labels[len(s.LabelNames)-1]
|
||||
if _, exists := targetMap[key]; exists {
|
||||
labelsBuf := bytes.Buffer{}
|
||||
for _, label := range childBlock.Labels {
|
||||
@ -673,7 +666,7 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
|
||||
return ctyMap(elems, len(s.LabelNames)), diags
|
||||
}
|
||||
|
||||
func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We return the source range of the _first_ block of the given type,
|
||||
// since they are not guaranteed to form a contiguous range.
|
||||
|
||||
@ -691,7 +684,7 @@ func (s *BlockMapSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) h
|
||||
return content.MissingItemRange
|
||||
}
|
||||
|
||||
return sourceRange(childBlock.Body, childBlock, s.Nested)
|
||||
return sourceRange(childBlock.Body, labelsForBlock(childBlock), s.Nested)
|
||||
}
|
||||
|
||||
// A BlockLabelSpec is a Spec that returns a cty.String representing the
|
||||
@ -714,16 +707,58 @@ func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
// leaf node
|
||||
}
|
||||
|
||||
func (s *BlockLabelSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
panic("BlockLabelSpec.decode not yet implemented")
|
||||
}
|
||||
|
||||
func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
if block == nil {
|
||||
func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
if s.Index >= len(blockLabels) {
|
||||
panic("BlockListSpec used in non-block context")
|
||||
}
|
||||
|
||||
return block.LabelRanges[s.Index]
|
||||
return cty.StringVal(blockLabels[s.Index].Value), nil
|
||||
}
|
||||
|
||||
func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
if s.Index >= len(blockLabels) {
|
||||
panic("BlockListSpec used in non-block context")
|
||||
}
|
||||
|
||||
return blockLabels[s.Index].Range
|
||||
}
|
||||
|
||||
func findLabelSpecs(spec Spec) []string {
|
||||
maxIdx := -1
|
||||
var names map[int]string
|
||||
|
||||
var visit visitFunc
|
||||
visit = func(s Spec) {
|
||||
if ls, ok := s.(*BlockLabelSpec); ok {
|
||||
if maxIdx < ls.Index {
|
||||
maxIdx = ls.Index
|
||||
}
|
||||
if names == nil {
|
||||
names = make(map[int]string)
|
||||
}
|
||||
names[ls.Index] = ls.Name
|
||||
}
|
||||
s.visitSameBodyChildren(visit)
|
||||
}
|
||||
|
||||
visit(spec)
|
||||
|
||||
if maxIdx < 0 {
|
||||
return nil // no labels at all
|
||||
}
|
||||
|
||||
ret := make([]string, maxIdx+1)
|
||||
for i := range ret {
|
||||
name := names[i]
|
||||
if name == "" {
|
||||
// Should never happen if the spec is conformant, since we require
|
||||
// consecutive indices starting at zero.
|
||||
name = fmt.Sprintf("missing%02d", i)
|
||||
}
|
||||
ret[i] = name
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// DefaultSpec is a spec that wraps two specs, evaluating the primary first
|
||||
@ -738,20 +773,20 @@ func (s *DefaultSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
cb(s.Default)
|
||||
}
|
||||
|
||||
func (s *DefaultSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
val, diags := s.Primary.decode(content, block, ctx)
|
||||
func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
val, diags := s.Primary.decode(content, blockLabels, ctx)
|
||||
if val.IsNull() {
|
||||
var moreDiags hcl.Diagnostics
|
||||
val, moreDiags = s.Default.decode(content, block, ctx)
|
||||
val, moreDiags = s.Default.decode(content, blockLabels, ctx)
|
||||
diags = append(diags, moreDiags...)
|
||||
}
|
||||
return val, diags
|
||||
}
|
||||
|
||||
func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
|
||||
func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We can't tell from here which of the two specs will ultimately be used
|
||||
// in our result, so we'll just assume the first. This is usually the right
|
||||
// choice because the default is often a literal spec that doesn't have a
|
||||
// reasonable source range to return anyway.
|
||||
return s.Primary.sourceRange(content, block)
|
||||
return s.Primary.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
@ -10,4 +10,5 @@ var blockSpecAsSpec Spec = (*BlockSpec)(nil)
|
||||
var blockListSpecAsSpec Spec = (*BlockListSpec)(nil)
|
||||
var blockSetSpecAsSpec Spec = (*BlockSetSpec)(nil)
|
||||
var blockMapSpecAsSpec Spec = (*BlockMapSpec)(nil)
|
||||
var blockLabelSpecAsSpec Spec = (*BlockLabelSpec)(nil)
|
||||
var defaultSpecAsSpec Spec = (*DefaultSpec)(nil)
|
||||
|
Loading…
Reference in New Issue
Block a user