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:
Martin Atkins 2017-10-03 15:59:20 -07:00
parent a6dff4e9f9
commit 0d6247f4cf
6 changed files with 248 additions and 65 deletions

21
hcldec/block_labels.go Normal file
View 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
}

View File

@ -5,7 +5,7 @@ import (
"github.com/zclconf/go-cty/cty" "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) schema := ImpliedSchema(spec)
var content *hcl.BodyContent 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) content, diags = body.Content(schema)
} }
val, valDiags := spec.decode(content, block, ctx) val, valDiags := spec.decode(content, blockLabels, ctx)
diags = append(diags, valDiags...) diags = append(diags, valDiags...)
return val, leftovers, diags 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) schema := ImpliedSchema(spec)
content, _, _ := body.PartialContent(schema) content, _, _ := body.PartialContent(schema)
return spec.sourceRange(content, block) return spec.sourceRange(content, blockLabels)
} }

View File

@ -18,5 +18,6 @@ func init() {
gob.Register((*BlockListSpec)(nil)) gob.Register((*BlockListSpec)(nil))
gob.Register((*BlockSetSpec)(nil)) gob.Register((*BlockSetSpec)(nil))
gob.Register((*BlockMapSpec)(nil)) gob.Register((*BlockMapSpec)(nil))
gob.Register((*BlockLabelSpec)(nil))
gob.Register((*DefaultSpec)(nil)) gob.Register((*DefaultSpec)(nil))
} }

View File

@ -148,6 +148,54 @@ b {
cty.EmptyObjectVal, cty.EmptyObjectVal,
0, 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{ &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 {} 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 "foo" {}
b "bar" {} 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})}), cty.MapVal(map[string]cty.Value{"foo": cty.MapVal(map[string]cty.Value{"bar": cty.EmptyObjectVal})}),
1, // duplicate b block 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 { for i, test := range tests {

View File

@ -20,7 +20,7 @@ type Spec interface {
// //
// "block" is provided only by the nested calls performed by the spec // "block" is provided only by the nested calls performed by the spec
// types that work on block bodies. // 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 // 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 // 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 // spec in the given content, in the context of the given block
// (which might be null). If the corresponding item is missing, return // (which might be null). If the corresponding item is missing, return
// a place where it might be inserted. // 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) 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)) vals := make(map[string]cty.Value, len(s))
var diags hcl.Diagnostics var diags hcl.Diagnostics
for k, spec := range s { for k, spec := range s {
var kd hcl.Diagnostics var kd hcl.Diagnostics
vals[k], kd = spec.decode(content, block, ctx) vals[k], kd = spec.decode(content, blockLabels, ctx)
diags = append(diags, kd...) diags = append(diags, kd...)
} }
return cty.ObjectVal(vals), diags return cty.ObjectVal(vals), diags
} }
func (s ObjectSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { func (s ObjectSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
if block != nil {
return block.DefRange
}
// This is not great, but the best we can do. In practice, it's rather // 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 // strange to ask for the source range of an entire top-level body, since
// that's already readily available to the caller. // 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)) vals := make([]cty.Value, len(s))
var diags hcl.Diagnostics var diags hcl.Diagnostics
for i, spec := range s { for i, spec := range s {
var ed hcl.Diagnostics var ed hcl.Diagnostics
vals[i], ed = spec.decode(content, block, ctx) vals[i], ed = spec.decode(content, blockLabels, ctx)
diags = append(diags, ed...) diags = append(diags, ed...)
} }
return cty.TupleVal(vals), diags return cty.TupleVal(vals), diags
} }
func (s TupleSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range { func (s TupleSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
if block != nil {
return block.DefRange
}
// This is not great, but the best we can do. In practice, it's rather // 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 // strange to ask for the source range of an entire top-level body, since
// that's already readily available to the caller. // 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] attr, exists := content.Attributes[s.Name]
if !exists { if !exists {
return content.MissingItemRange return content.MissingItemRange
@ -162,7 +154,7 @@ func (s *AttrSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.R
return attr.Expr.Range() 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] attr, exists := content.Attributes[s.Name]
if !exists { if !exists {
// We don't need to check required and emit a diagnostic here, because // 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 // 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 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 // No sensible range to return for a literal, so the caller had better
// ensure it doesn't cause any diagnostics. // ensure it doesn't cause any diagnostics.
return hcl.Range{ return hcl.Range{
@ -231,11 +223,11 @@ func (s *ExprSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
return s.Expr.Variables() 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) 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() return s.Expr.Range()
} }
@ -260,6 +252,7 @@ func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
return []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) 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 diags hcl.Diagnostics
var childBlock *hcl.Block var childBlock *hcl.Block
@ -325,12 +318,12 @@ func (s *BlockSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.
if s.Nested == nil { if s.Nested == nil {
panic("BlockSpec with no Nested Spec") 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...) diags = append(diags, childDiags...)
return val, diags 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 var childBlock *hcl.Block
for _, candidate := range content.Blocks { for _, candidate := range content.Blocks {
if candidate.Type != s.TypeName { if candidate.Type != s.TypeName {
@ -345,7 +338,7 @@ func (s *BlockSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.
return content.MissingItemRange 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 // A BlockListSpec is a Spec that produces a cty list of the results of
@ -366,9 +359,7 @@ func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
return []hcl.BlockHeaderSchema{ return []hcl.BlockHeaderSchema{
{ {
Type: s.TypeName, Type: s.TypeName,
// FIXME: Need to peek into s.Nested to see if it has any LabelNames: findLabelSpecs(s.Nested),
// BlockLabelSpec instances, which will define then how many
// labels we need.
}, },
} }
} }
@ -388,7 +379,7 @@ func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversa
return ret 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 var diags hcl.Diagnostics
if s.Nested == nil { if s.Nested == nil {
@ -402,10 +393,10 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
continue 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...) diags = append(diags, childDiags...)
elems = append(elems, val) 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 { if len(elems) < s.MinItems {
@ -437,7 +428,7 @@ func (s *BlockListSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *
return ret, diags 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, // We return the source range of the _first_ block of the given type,
// since they are not guaranteed to form a contiguous range. // 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 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 // A BlockSetSpec is a Spec that produces a cty set of the results of
@ -476,6 +467,7 @@ func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
return []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 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 var diags hcl.Diagnostics
if s.Nested == nil { if s.Nested == nil {
@ -509,10 +501,10 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
continue 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...) diags = append(diags, childDiags...)
elems = append(elems, val) 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 { if len(elems) < s.MinItems {
@ -544,7 +536,7 @@ func (s *BlockSetSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
return ret, diags 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, // We return the source range of the _first_ block of the given type,
// since they are not guaranteed to form a contiguous range. // 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 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 // 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{ return []hcl.BlockHeaderSchema{
{ {
Type: s.TypeName, 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 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 var diags hcl.Diagnostics
if s.Nested == nil { if s.Nested == nil {
@ -618,9 +610,10 @@ func (s *BlockMapSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *h
continue 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 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 { if _, exists := targetMap[key]; !exists {
targetMap[key] = make(map[string]interface{}) 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...) diags = append(diags, childDiags...)
key := childBlock.Labels[len(childBlock.Labels)-1] key := childBlock.Labels[len(s.LabelNames)-1]
if _, exists := targetMap[key]; exists { if _, exists := targetMap[key]; exists {
labelsBuf := bytes.Buffer{} labelsBuf := bytes.Buffer{}
for _, label := range childBlock.Labels { 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 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, // We return the source range of the _first_ block of the given type,
// since they are not guaranteed to form a contiguous range. // 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 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 // A BlockLabelSpec is a Spec that returns a cty.String representing the
@ -714,16 +707,58 @@ func (s *BlockLabelSpec) visitSameBodyChildren(cb visitFunc) {
// leaf node // leaf node
} }
func (s *BlockLabelSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { func (s *BlockLabelSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
panic("BlockLabelSpec.decode not yet implemented") if s.Index >= len(blockLabels) {
}
func (s *BlockLabelSpec) sourceRange(content *hcl.BodyContent, block *hcl.Block) hcl.Range {
if block == nil {
panic("BlockListSpec used in non-block context") 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 // 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) cb(s.Default)
} }
func (s *DefaultSpec) decode(content *hcl.BodyContent, block *hcl.Block, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { func (s *DefaultSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
val, diags := s.Primary.decode(content, block, ctx) val, diags := s.Primary.decode(content, blockLabels, ctx)
if val.IsNull() { if val.IsNull() {
var moreDiags hcl.Diagnostics var moreDiags hcl.Diagnostics
val, moreDiags = s.Default.decode(content, block, ctx) val, moreDiags = s.Default.decode(content, blockLabels, ctx)
diags = append(diags, moreDiags...) diags = append(diags, moreDiags...)
} }
return val, diags 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 // 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 // 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 // choice because the default is often a literal spec that doesn't have a
// reasonable source range to return anyway. // reasonable source range to return anyway.
return s.Primary.sourceRange(content, block) return s.Primary.sourceRange(content, blockLabels)
} }

View File

@ -10,4 +10,5 @@ var blockSpecAsSpec Spec = (*BlockSpec)(nil)
var blockListSpecAsSpec Spec = (*BlockListSpec)(nil) var blockListSpecAsSpec Spec = (*BlockListSpec)(nil)
var blockSetSpecAsSpec Spec = (*BlockSetSpec)(nil) var blockSetSpecAsSpec Spec = (*BlockSetSpec)(nil)
var blockMapSpecAsSpec Spec = (*BlockMapSpec)(nil) var blockMapSpecAsSpec Spec = (*BlockMapSpec)(nil)
var blockLabelSpecAsSpec Spec = (*BlockLabelSpec)(nil)
var defaultSpecAsSpec Spec = (*DefaultSpec)(nil) var defaultSpecAsSpec Spec = (*DefaultSpec)(nil)