Skip to content

Commit 55c3d40

Browse files
Add blocks tag that treats a slice as multiple separate blocks.
1 parent 2ce667c commit 55c3d40

8 files changed

Lines changed: 65 additions & 9 deletions

File tree

.idea/.gitignore

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/hclencoder.iml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

_tests/repeated-blocks.hcl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Widget {}
2+
3+
Widget {}

hclencoder_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ func TestEncoder(t *testing.T) {
6161
},
6262
Output: "primitive-lists",
6363
},
64+
{
65+
ID: "repeated blocks",
66+
Input: struct {
67+
Widget []struct{} `hcl:",blocks"`
68+
}{
69+
[]struct{}{{}, {}},
70+
},
71+
Output: "repeated-blocks",
72+
},
6473
{
6574
ID: "nested struct",
6675
Input: struct {

nodes.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ const (
2727
// the key for the value.
2828
SquashTag string = "squash"
2929

30+
// Blocks is attached to a slice of objects and indicates that
31+
// the slice should be treated as multiple separate blocks rather than
32+
// a list.
33+
Blocks string = "blocks"
34+
3035
// UnusedKeysTag is a flag that indicates any unused keys found by the
3136
// decoder are stored in this field of type []string. This has the same
3237
// behavior as the OmitTag and is not encoded.
@@ -55,14 +60,19 @@ type fieldMeta struct {
5560
name string
5661
key bool
5762
squash bool
63+
repeatBlock bool
5864
unusedKeys bool
5965
decodedFields bool
6066
omit bool
6167
omitEmpty bool
6268
}
6369

64-
// encode converts a reflected valued into an HCL ast.Node in a depth-first manner.
6570
func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
71+
return encodeField(in, fieldMeta{})
72+
}
73+
74+
// encode converts a reflected valued into an HCL ast.Node in a depth-first manner.
75+
func encodeField(in reflect.Value, meta fieldMeta) (node ast.Node, key []*ast.ObjectKey, err error) {
6676
in, isNil := deref(in)
6777
if isNil {
6878
return nil, nil, nil
@@ -76,7 +86,7 @@ func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
7686
return encodePrimitive(in)
7787

7888
case reflect.Slice:
79-
return encodeList(in)
89+
return encodeList(in, meta.repeatBlock)
8090

8191
case reflect.Map:
8292
return encodeMap(in)
@@ -87,7 +97,6 @@ func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
8797
default:
8898
return nil, nil, fmt.Errorf("cannot encode kind %s to HCL", in.Kind())
8999
}
90-
91100
}
92101

93102
// encodePrimitive converts a primitive value into an ast.LiteralType. An
@@ -103,7 +112,7 @@ func encodePrimitive(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
103112

104113
// encodeList converts a slice to an appropriate ast.Node type depending on its
105114
// element value type. An ast.ObjectKey is never returned.
106-
func encodeList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
115+
func encodeList(in reflect.Value, repeatBlock bool) (ast.Node, []*ast.ObjectKey, error) {
107116
childType := in.Type().Elem()
108117

109118
childLoop:
@@ -118,7 +127,7 @@ childLoop:
118127

119128
switch childType.Kind() {
120129
case reflect.Map, reflect.Struct, reflect.Interface:
121-
return encodeBlockList(in)
130+
return encodeBlockList(in, repeatBlock)
122131
default:
123132
return encodePrimitiveList(in)
124133
}
@@ -145,7 +154,7 @@ func encodePrimitiveList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
145154

146155
// encodeBlockList converts a slice of non-primitive types to an ast.ObjectList. An
147156
// ast.ObjectKey is never returned.
148-
func encodeBlockList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
157+
func encodeBlockList(in reflect.Value, repeatBlock bool) (ast.Node, []*ast.ObjectKey, error) {
149158
l := in.Len()
150159
n := &ast.ObjectList{Items: make([]*ast.ObjectItem, 0, l)}
151160

@@ -157,7 +166,7 @@ func encodeBlockList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
157166
if child == nil {
158167
continue
159168
}
160-
if childKey == nil {
169+
if childKey == nil && !repeatBlock {
161170
return encodePrimitiveList(in)
162171
}
163172

@@ -248,7 +257,7 @@ func encodeStruct(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
248257
}
249258
}
250259

251-
val, childKeys, err := encode(rawVal)
260+
val, childKeys, err := encodeField(rawVal, meta)
252261
if err != nil {
253262
return nil, nil, err
254263
}
@@ -375,6 +384,8 @@ func extractFieldMeta(f reflect.StructField) (meta fieldMeta) {
375384
meta.decodedFields = true
376385
case UnusedKeysTag:
377386
meta.unusedKeys = true
387+
case Blocks:
388+
meta.repeatBlock = true
378389
}
379390
}
380391
}

nodes_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,9 @@ func TestEncodeList(t *testing.T) {
237237
},
238238
}
239239

240-
RunAll(tests, encodeList, t)
240+
RunAll(tests, func(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
241+
return encodeList(in, false)
242+
}, t)
241243
}
242244

243245
func TestEncodeMap(t *testing.T) {

0 commit comments

Comments
 (0)