Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.

Commit cf76147

Browse files
committed
Add support for Nodes
1 parent fb9757b commit cf76147

File tree

8 files changed

+68
-26
lines changed

8 files changed

+68
-26
lines changed

_tests/basic.hcl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ Int = 123
55
Bool = true
66

77
Float = 4.56
8+
9+
Node = baz

_tests/nested-structs.hcl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ Foo {
55
Fizz {
66
Buzz = 1.23
77
}
8+
9+
Bar {
10+
Bar = baz
11+
}

hclencoder.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
// Encode converts any supported type into the corresponding HCL format
1212
func Encode(in interface{}) ([]byte, error) {
13-
node, _, err := encode(reflect.ValueOf(in))
13+
node, _, err := encode(reflect.ValueOf(in), false)
1414
if err != nil {
1515
return nil, err
1616
}

hclencoder_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ func TestEncoder(t *testing.T) {
2929
Int int
3030
Bool bool
3131
Float float64
32+
Node string `hcle:"node"`
3233
}{
3334
"bar",
3435
123,
3536
true,
3637
4.56,
38+
"baz",
3739
},
3840
Output: "basic",
3941
},
@@ -66,9 +68,15 @@ func TestEncoder(t *testing.T) {
6668
Input: struct {
6769
Foo struct{ Bar string }
6870
Fizz struct{ Buzz float64 }
71+
Bar struct {
72+
Bar string `hcle:"node"`
73+
}
6974
}{
7075
struct{ Bar string }{Bar: "baz"},
7176
struct{ Buzz float64 }{Buzz: 1.23},
77+
struct {
78+
Bar string `hcle:"node"`
79+
}{Bar: "baz"},
7280
},
7381
Output: "nested-structs",
7482
},

nodes.go

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const (
4848
// OmitEmptyTag will omit this field if it is a zero value. This
4949
// is similar behavior to `json:",omitempty"`
5050
OmitEmptyTag string = "omitempty"
51+
52+
// Node will omit quotes from the output, useful for references.
53+
Node string = "node"
5154
)
5255

5356
type fieldMeta struct {
@@ -59,10 +62,11 @@ type fieldMeta struct {
5962
decodedFields bool
6063
omit bool
6164
omitEmpty bool
65+
node bool
6266
}
6367

6468
// encode converts a reflected valued into an HCL ast.Node in a depth-first manner.
65-
func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
69+
func encode(in reflect.Value, isNode bool) (node ast.Node, key []*ast.ObjectKey, err error) {
6670
in, isNil := deref(in)
6771
if isNil {
6872
return nil, nil, nil
@@ -73,16 +77,16 @@ func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
7377
case reflect.Bool, reflect.Float64, reflect.String,
7478
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
7579
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
76-
return encodePrimitive(in)
80+
return encodePrimitive(in, isNode)
7781

7882
case reflect.Slice:
79-
return encodeList(in)
83+
return encodeList(in, isNode)
8084

8185
case reflect.Map:
82-
return encodeMap(in)
86+
return encodeMap(in, isNode)
8387

8488
case reflect.Struct:
85-
return encodeStruct(in)
89+
return encodeStruct(in, isNode)
8690

8791
default:
8892
return nil, nil, fmt.Errorf("cannot encode kind %s to HCL", in.Kind())
@@ -92,8 +96,8 @@ func encode(in reflect.Value) (node ast.Node, key []*ast.ObjectKey, err error) {
9296

9397
// encodePrimitive converts a primitive value into an ast.LiteralType. An
9498
// ast.ObjectKey is never returned.
95-
func encodePrimitive(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
96-
tkn, err := tokenize(in, false)
99+
func encodePrimitive(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
100+
tkn, err := tokenize(in, isNode)
97101
if err != nil {
98102
return nil, nil, err
99103
}
@@ -103,7 +107,7 @@ func encodePrimitive(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
103107

104108
// encodeList converts a slice to an appropriate ast.Node type depending on its
105109
// element value type. An ast.ObjectKey is never returned.
106-
func encodeList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
110+
func encodeList(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
107111
childType := in.Type().Elem()
108112

109113
childLoop:
@@ -118,20 +122,20 @@ childLoop:
118122

119123
switch childType.Kind() {
120124
case reflect.Map, reflect.Struct, reflect.Interface:
121-
return encodeBlockList(in)
125+
return encodeBlockList(in, isNode)
122126
default:
123-
return encodePrimitiveList(in)
127+
return encodePrimitiveList(in, isNode)
124128
}
125129
}
126130

127131
// encodePrimitiveList converts a slice of primitive values to an ast.ListType. An
128132
// ast.ObjectKey is never returned.
129-
func encodePrimitiveList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
133+
func encodePrimitiveList(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
130134
l := in.Len()
131135
n := &ast.ListType{List: make([]ast.Node, 0, l)}
132136

133137
for i := 0; i < l; i++ {
134-
child, _, err := encode(in.Index(i))
138+
child, _, err := encode(in.Index(i), isNode)
135139
if err != nil {
136140
return nil, nil, err
137141
}
@@ -145,20 +149,20 @@ func encodePrimitiveList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
145149

146150
// encodeBlockList converts a slice of non-primitive types to an ast.ObjectList. An
147151
// ast.ObjectKey is never returned.
148-
func encodeBlockList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
152+
func encodeBlockList(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
149153
l := in.Len()
150154
n := &ast.ObjectList{Items: make([]*ast.ObjectItem, 0, l)}
151155

152156
for i := 0; i < l; i++ {
153-
child, childKey, err := encode(in.Index(i))
157+
child, childKey, err := encode(in.Index(i), isNode)
154158
if err != nil {
155159
return nil, nil, err
156160
}
157161
if child == nil {
158162
continue
159163
}
160164
if childKey == nil {
161-
return encodePrimitiveList(in)
165+
return encodePrimitiveList(in, isNode)
162166
}
163167

164168
item := &ast.ObjectItem{Val: child}
@@ -171,7 +175,7 @@ func encodeBlockList(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
171175

172176
// encodeMap converts a map type into an ast.ObjectType. Maps must have string
173177
// key values to be encoded. An ast.ObjectKey is never returned.
174-
func encodeMap(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
178+
func encodeMap(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
175179
if keyType := in.Type().Key().Kind(); keyType != reflect.String {
176180
return nil, nil, fmt.Errorf("map keys must be strings, %s given", keyType)
177181
}
@@ -180,7 +184,7 @@ func encodeMap(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
180184
for _, key := range in.MapKeys() {
181185
tkn, _ := tokenize(key, true) // error impossible since we've already checked key kind
182186

183-
val, childKey, err := encode(in.MapIndex(key))
187+
val, childKey, err := encode(in.MapIndex(key), isNode)
184188
if err != nil {
185189
return nil, nil, err
186190
}
@@ -222,7 +226,7 @@ func encodeMap(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
222226
// encodeStruct converts a struct type into an ast.ObjectType. An ast.ObjectKey
223227
// may be returned if a KeyTag is present that should be used by a parent
224228
// ast.ObjectItem if this node is nested.
225-
func encodeStruct(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
229+
func encodeStruct(in reflect.Value, isNode bool) (ast.Node, []*ast.ObjectKey, error) {
226230
l := in.NumField()
227231
list := &ast.ObjectList{Items: make([]*ast.ObjectItem, 0, l)}
228232
keys := make([]*ast.ObjectKey, 0)
@@ -248,7 +252,8 @@ func encodeStruct(in reflect.Value) (ast.Node, []*ast.ObjectKey, error) {
248252
}
249253
}
250254

251-
val, childKeys, err := encode(rawVal)
255+
isNode = isNode || meta.node
256+
val, childKeys, err := encode(rawVal, isNode)
252257
if err != nil {
253258
return nil, nil, err
254259
}
@@ -386,6 +391,8 @@ func extractFieldMeta(f reflect.StructField) (meta fieldMeta) {
386391
meta.omit = true
387392
case OmitEmptyTag:
388393
meta.omitEmpty = true
394+
case Node:
395+
meta.node = true
389396
}
390397
}
391398

nodes_test.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13-
type encodeFunc func(reflect.Value) (ast.Node, []*ast.ObjectKey, error)
13+
type encodeFunc func(reflect.Value, bool) (ast.Node, []*ast.ObjectKey, error)
1414

1515
type encodeTest struct {
1616
ID string
1717
Input reflect.Value
18+
Node bool
1819
Expected ast.Node
1920
Key []*ast.ObjectKey
2021
Error bool
2122
}
2223

2324
func (test encodeTest) Test(f encodeFunc, t *testing.T) (node ast.Node, key []*ast.ObjectKey, err error) {
24-
node, key, err = f(test.Input)
25+
node, key, err = f(test.Input, test.Node)
2526

2627
if test.Error {
2728
assert.Error(t, err, test.ID)
@@ -113,6 +114,12 @@ func TestEncodePrimitive(t *testing.T) {
113114
Input: reflect.ValueOf("foobar"),
114115
Expected: &ast.LiteralType{Token: token.Token{Type: token.STRING, Text: `"foobar"`}},
115116
},
117+
{
118+
ID: "string - always ident",
119+
Input: reflect.ValueOf("foobar"),
120+
Node: true,
121+
Expected: &ast.LiteralType{Token: token.Token{Type: token.IDENT, Text: `foobar`}},
122+
},
116123
{
117124
ID: "uint",
118125
Input: reflect.ValueOf(uint(1)),
@@ -546,6 +553,10 @@ func TestExtractFieldMeta(t *testing.T) {
546553
`hcle:"omitempty"`,
547554
fieldMeta{name: fieldName, omitEmpty: true},
548555
},
556+
{
557+
`hcle:"node"`,
558+
fieldMeta{name: fieldName, node: true},
559+
},
549560
}
550561

551562
for _, test := range tests {

readme.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ type Farmer struct {
1616
}
1717

1818
type Animal struct {
19-
Name string `hcl:",key"`
20-
Sound string `hcl:"says" hcle:"omitempty"`
19+
Name string `hcl:",key"`
20+
Sound string `hcl:"says" hcle:"omitempty"`
21+
Category string `hcle:"node"`
2122
}
2223

2324
type Config struct {
@@ -42,13 +43,16 @@ input := Config{
4243
{
4344
Name: "cow",
4445
Sound: "moo",
46+
Category: "data.animal_categories.cow"
4547
},
4648
{
4749
Name: "pig",
4850
Sound: "oink",
51+
Category: "data.animal_categories.pig"
4952
},
5053
{
5154
Name: "rock",
55+
Category: "data.animal_categories.rock"
5256
},
5357
},
5458
Buildings: map[string]string{
@@ -81,13 +85,17 @@ fmt.Print(string(hcl))
8185
//
8286
// animal "cow" {
8387
// says = "moo"
88+
// category = data.animal_categories.cow
8489
// }
8590
//
8691
// animal "pig" {
8792
// says = "oink"
93+
// category = data.animal_categories.pig
8894
// }
8995
//
90-
// animal "rock" {}
96+
// animal "rock" {
97+
// category = data.animal_categories.rock
98+
// }
9199
//
92100
// buildings {
93101
// Barn = "456 Digits Drive"
@@ -126,6 +134,8 @@ fmt.Print(string(hcl))
126134

127135
- **`hcle:"omitempty"`** - omits this field if it is a zero value for its type. This is similar behavior to [`json:",omitempty"`][json].
128136

137+
- **`hcle:"node"`** - node will omit quotes from the output, useful for references.
138+
129139
[HCL]: https://github.com/hashicorp/hcl
130140
[hclprinter]: https://godoc.org/github.com/hashicorp/hcl/hcl/printer
131141
[json]: https://golang.org/pkg/encoding/json/#Marshal

script/test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ echo "go lint..."
1313
test -z "$(golint ./... | tee /dev/stderr)"
1414

1515
echo "go vet..."
16-
test -z "$(go tool vet -all -shadow . 2>&1 | tee /dev/stderr)"
16+
test -z "$(go vet -all . 2>&1 | tee /dev/stderr)"
1717

1818
echo "go test..."
1919
go test -race -cover ./...

0 commit comments

Comments
 (0)