hcl/parser: Support lists of objects
a.k.a lists of maps Implementation was pretty straightforward - I had to tweak the `needsComma` handling since it was stuck inside literal parsing. It happens out front now. I also promoted the `assign_deep.hcl` parser test to a decoder test that passes, since it was testing for an error to occur but now it works! :) Additionally we make ObjectLists support being comma-delimited, which enables maps to defined inline like `{one = 1, two = 2}`. Refs https://github.com/hashicorp/terraform/issues/7142
This commit is contained in:
parent
61f5143284
commit
92237bfa68
@ -194,6 +194,27 @@ func TestDecode_interface(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"list_of_maps.hcl",
|
||||||
|
false,
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{
|
||||||
|
map[string]interface{}{"somekey1": "someval1"},
|
||||||
|
map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"assign_deep.hcl",
|
||||||
|
false,
|
||||||
|
map[string]interface{}{
|
||||||
|
"resource": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"foo": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"bar": []map[string]interface{}{
|
||||||
|
map[string]interface{}{}}}}}}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"structure_list.hcl",
|
"structure_list.hcl",
|
||||||
false,
|
false,
|
||||||
|
@ -79,6 +79,13 @@ func (p *Parser) objectList() (*ast.ObjectList, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.Add(n)
|
node.Add(n)
|
||||||
|
|
||||||
|
// object lists can be optionally comma-delimited e.g. when a list of maps
|
||||||
|
// is being expressed, so a comma is allowed here - it's simply consumed
|
||||||
|
tok := p.scan()
|
||||||
|
if tok.Type != token.COMMA {
|
||||||
|
p.unscan()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return node, nil
|
return node, nil
|
||||||
}
|
}
|
||||||
@ -311,15 +318,20 @@ func (p *Parser) listType() (*ast.ListType, error) {
|
|||||||
needComma := false
|
needComma := false
|
||||||
for {
|
for {
|
||||||
tok := p.scan()
|
tok := p.scan()
|
||||||
switch tok.Type {
|
if needComma {
|
||||||
case token.NUMBER, token.FLOAT, token.STRING, token.HEREDOC:
|
switch tok.Type {
|
||||||
if needComma {
|
case token.COMMA, token.RBRACK:
|
||||||
|
default:
|
||||||
return nil, &PosError{
|
return nil, &PosError{
|
||||||
Pos: tok.Pos,
|
Pos: tok.Pos,
|
||||||
Err: fmt.Errorf("unexpected token: %s. Expecting %s", tok.Type, token.COMMA),
|
Err: fmt.Errorf(
|
||||||
|
"error parsing list, expected comma or list end, got: %s",
|
||||||
|
tok.Type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
switch tok.Type {
|
||||||
|
case token.NUMBER, token.FLOAT, token.STRING, token.HEREDOC:
|
||||||
node, err := p.literalType()
|
node, err := p.literalType()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -343,6 +355,18 @@ func (p *Parser) listType() (*ast.ListType, error) {
|
|||||||
|
|
||||||
needComma = false
|
needComma = false
|
||||||
continue
|
continue
|
||||||
|
case token.LBRACE:
|
||||||
|
// Looks like a nested object, so parse it out
|
||||||
|
node, err := p.objectType()
|
||||||
|
if err != nil {
|
||||||
|
return nil, &PosError{
|
||||||
|
Pos: tok.Pos,
|
||||||
|
Err: fmt.Errorf(
|
||||||
|
"error while trying to parse object within list: %s", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.Add(node)
|
||||||
|
needComma = true
|
||||||
case token.BOOL:
|
case token.BOOL:
|
||||||
// TODO(arslan) should we support? not supported by HCL yet
|
// TODO(arslan) should we support? not supported by HCL yet
|
||||||
case token.LBRACK:
|
case token.LBRACK:
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/hcl/ast"
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
@ -99,6 +100,58 @@ EOF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListOfMaps(t *testing.T) {
|
||||||
|
src := `foo = [
|
||||||
|
{key = "bar"},
|
||||||
|
{key = "baz", key2 = "qux"},
|
||||||
|
]`
|
||||||
|
p := newParser([]byte(src))
|
||||||
|
|
||||||
|
file, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we make all sorts of assumptions about the input structure w/ type
|
||||||
|
// assertions. The intent is only for this to be a "smoke test" ensuring
|
||||||
|
// parsing actually performed its duty - giving this test something a bit
|
||||||
|
// more robust than _just_ "no error occurred".
|
||||||
|
expected := []string{`"bar"`, `"baz"`, `"qux"`}
|
||||||
|
actual := make([]string, 0, 3)
|
||||||
|
ol := file.Node.(*ast.ObjectList)
|
||||||
|
objItem := ol.Items[0]
|
||||||
|
list := objItem.Val.(*ast.ListType)
|
||||||
|
for _, node := range list.List {
|
||||||
|
obj := node.(*ast.ObjectType)
|
||||||
|
for _, item := range obj.List.Items {
|
||||||
|
val := item.Val.(*ast.LiteralType)
|
||||||
|
actual = append(actual, val.Token.Text)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Fatalf("Expected: %#v, got %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListOfMaps_requiresComma(t *testing.T) {
|
||||||
|
src := `foo = [
|
||||||
|
{key = "bar"}
|
||||||
|
{key = "baz"}
|
||||||
|
]`
|
||||||
|
p := newParser([]byte(src))
|
||||||
|
|
||||||
|
_, err := p.Parse()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error, got none!")
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "error parsing list, expected comma or list end"
|
||||||
|
if !strings.Contains(err.Error(), expected) {
|
||||||
|
t.Fatalf("Expected err:\n %s\nTo contain:\n %s\n", err, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestObjectType(t *testing.T) {
|
func TestObjectType(t *testing.T) {
|
||||||
var literals = []struct {
|
var literals = []struct {
|
||||||
src string
|
src string
|
||||||
@ -263,6 +316,10 @@ func TestParse(t *testing.T) {
|
|||||||
"multiple.hcl",
|
"multiple.hcl",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"object_list_comma.hcl",
|
||||||
|
false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"structure.hcl",
|
"structure.hcl",
|
||||||
false,
|
false,
|
||||||
@ -279,10 +336,6 @@ func TestParse(t *testing.T) {
|
|||||||
"complex.hcl",
|
"complex.hcl",
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"assign_deep.hcl",
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"types.hcl",
|
"types.hcl",
|
||||||
false,
|
false,
|
||||||
|
1
hcl/parser/test-fixtures/object_list_comma.hcl
Normal file
1
hcl/parser/test-fixtures/object_list_comma.hcl
Normal file
@ -0,0 +1 @@
|
|||||||
|
foo = {one = 1, two = 2}
|
4
test-fixtures/list_of_maps.hcl
Normal file
4
test-fixtures/list_of_maps.hcl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
foo = [
|
||||||
|
{somekey1 = "someval1"},
|
||||||
|
{somekey2 = "someval2", someextrakey = "someextraval"},
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user