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"
|
"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)
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
151
hcldec/spec.go
151
hcldec/spec.go
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user