hclpack: PackNativeFile to get a packed version of a native syntax file

This is a straightforward way to get a hclpack.Body in the common case
where the input is already native syntax source code. Since the native
syntax is unambiguous about structure, the whole structure can be packed
in a single pass with no further information.
This commit is contained in:
Martin Atkins 2018-11-11 00:00:53 +00:00
parent 227ba9e86b
commit ed7453e277
3 changed files with 233 additions and 0 deletions

58
hclpack/pack_native.go Normal file
View File

@ -0,0 +1,58 @@
package hclpack
import (
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
)
// PackNativeFile parses the given source code as HCL native syntax and packs
// it into a hclpack Body ready to be marshalled.
//
// If the given source code contains syntax errors then error diagnostics will
// be returned. A non-nil body might still be returned in this case, which
// allows a cautious caller to still do certain analyses on the result.
func PackNativeFile(src []byte, filename string, start hcl.Pos) (*Body, hcl.Diagnostics) {
f, diags := hclsyntax.ParseConfig(src, filename, start)
rootBody := f.Body.(*hclsyntax.Body)
return packNativeBody(rootBody, src), diags
}
func packNativeBody(body *hclsyntax.Body, src []byte) *Body {
ret := &Body{}
for name, attr := range body.Attributes {
exprRng := attr.Expr.Range()
exprStartRng := attr.Expr.StartRange()
exprSrc := exprRng.SliceBytes(src)
ret.setAttribute(name, Attribute{
Expr: Expression{
Source: exprSrc,
SourceType: ExprNative,
Range_: exprRng,
StartRange_: exprStartRng,
},
Range: attr.Range(),
NameRange: attr.NameRange,
})
}
for _, block := range body.Blocks {
childBody := packNativeBody(block.Body, src)
defRange := block.TypeRange
if len(block.LabelRanges) > 0 {
defRange = hcl.RangeBetween(defRange, block.LabelRanges[len(block.LabelRanges)-1])
}
ret.appendBlock(Block{
Type: block.Type,
Labels: block.Labels,
Body: *childBody,
TypeRange: block.TypeRange,
DefRange: defRange,
LabelRanges: block.LabelRanges,
})
}
ret.MissingItemRange_ = body.EndRange
return ret
}

166
hclpack/pack_native_test.go Normal file
View File

@ -0,0 +1,166 @@
package hclpack
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl2/hcl"
)
func TestPackNativeFile(t *testing.T) {
src := `
foo = "bar"
baz = "boz"
child {
a = b + c
}
another_child "foo" "bar" {}
`
got, diags := PackNativeFile([]byte(src), "", hcl.Pos{Line: 1, Column: 1})
for _, diag := range diags {
t.Errorf("unexpected diagnostic: %s", diag.Error())
}
want := &Body{
Attributes: map[string]Attribute{
"baz": {
Expr: Expression{
Source: []byte(`"boz"`),
SourceType: ExprNative,
Range_: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 7, Byte: 19},
End: hcl.Pos{Line: 3, Column: 12, Byte: 24},
},
StartRange_: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 8, Byte: 20},
End: hcl.Pos{Line: 3, Column: 11, Byte: 23},
},
},
Range: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 1, Byte: 13},
End: hcl.Pos{Line: 3, Column: 12, Byte: 24},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 3, Column: 1, Byte: 13},
End: hcl.Pos{Line: 3, Column: 4, Byte: 16},
},
},
"foo": {
Expr: Expression{
Source: []byte(`"bar"`),
SourceType: ExprNative,
Range_: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 7, Byte: 7},
End: hcl.Pos{Line: 2, Column: 12, Byte: 12},
},
StartRange_: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 8, Byte: 8},
End: hcl.Pos{Line: 2, Column: 11, Byte: 11},
},
},
Range: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 1, Byte: 1},
End: hcl.Pos{Line: 2, Column: 12, Byte: 12},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 2, Column: 1, Byte: 1},
End: hcl.Pos{Line: 2, Column: 4, Byte: 4},
},
},
},
ChildBlocks: []Block{
{
Type: "child",
Body: Body{
Attributes: map[string]Attribute{
"a": {
Expr: Expression{
Source: []byte(`b + c`),
SourceType: ExprNative,
Range_: hcl.Range{
Start: hcl.Pos{Line: 6, Column: 7, Byte: 40},
End: hcl.Pos{Line: 6, Column: 12, Byte: 45},
},
StartRange_: hcl.Range{
Start: hcl.Pos{Line: 6, Column: 7, Byte: 40},
End: hcl.Pos{Line: 6, Column: 8, Byte: 41},
},
},
Range: hcl.Range{
Start: hcl.Pos{Line: 6, Column: 3, Byte: 36},
End: hcl.Pos{Line: 6, Column: 12, Byte: 45},
},
NameRange: hcl.Range{
Start: hcl.Pos{Line: 6, Column: 3, Byte: 36},
End: hcl.Pos{Line: 6, Column: 4, Byte: 37},
},
},
},
MissingItemRange_: hcl.Range{
Start: hcl.Pos{Line: 7, Column: 2, Byte: 47},
End: hcl.Pos{Line: 7, Column: 2, Byte: 47},
},
},
DefRange: hcl.Range{
Start: hcl.Pos{Line: 5, Column: 1, Byte: 26},
End: hcl.Pos{Line: 5, Column: 6, Byte: 31},
},
TypeRange: hcl.Range{
Start: hcl.Pos{Line: 5, Column: 1, Byte: 26},
End: hcl.Pos{Line: 5, Column: 6, Byte: 31},
},
},
{
Type: "another_child",
Labels: []string{"foo", "bar"},
Body: Body{
MissingItemRange_: hcl.Range{
Start: hcl.Pos{Line: 9, Column: 29, Byte: 77},
End: hcl.Pos{Line: 9, Column: 29, Byte: 77},
},
},
DefRange: hcl.Range{
Start: hcl.Pos{Line: 9, Column: 1, Byte: 49},
End: hcl.Pos{Line: 9, Column: 26, Byte: 74},
},
TypeRange: hcl.Range{
Start: hcl.Pos{Line: 9, Column: 1, Byte: 49},
End: hcl.Pos{Line: 9, Column: 14, Byte: 62},
},
LabelRanges: []hcl.Range{
hcl.Range{
Start: hcl.Pos{Line: 9, Column: 15, Byte: 63},
End: hcl.Pos{Line: 9, Column: 20, Byte: 68},
},
hcl.Range{
Start: hcl.Pos{Line: 9, Column: 21, Byte: 69},
End: hcl.Pos{Line: 9, Column: 26, Byte: 74},
},
},
},
},
MissingItemRange_: hcl.Range{
Start: hcl.Pos{Line: 10, Column: 1, Byte: 78},
End: hcl.Pos{Line: 10, Column: 1, Byte: 78},
},
}
if !cmp.Equal(want, got) {
bytesAsString := func(s []byte) string {
return string(s)
}
posAsString := func(pos hcl.Pos) string {
return fmt.Sprintf("%#v", pos)
}
t.Errorf("wrong result\n%s", cmp.Diff(
want, got,
cmp.Transformer("bytesAsString", bytesAsString),
cmp.Transformer("posAsString", posAsString),
))
}
}

View File

@ -17,6 +17,10 @@ type Body struct {
var _ hcl.Body = (*Body)(nil)
// Content is an implementation of the method of the same name on hcl.Body.
//
// When Content is called directly on a hclpack.Body, all child block bodies
// are guaranteed to be of type *hclpack.Body, so callers can type-assert
// to obtain a child Body in order to serialize it separately if needed.
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
return b.content(schema, nil)
}
@ -26,6 +30,11 @@ func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostic
// The returned "remain" body may share some backing objects with the receiver,
// so neither the receiver nor the returned remain body, or any descendent
// objects within them, may be mutated after this method is used.
//
// When Content is called directly on a hclpack.Body, all child block bodies
// and the returned "remain" body are guaranteed to be of type *hclpack.Body,
// so callers can type-assert to obtain a child Body in order to serialize it
// separately if needed.
func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
remain := &Body{}
content, diags := b.content(schema, remain)