hclpack: Remove

This package remains highly experimental, and so we'll remove it for now
and consider spinning it off into its own repository for further iteration
if the direction seems promising.
This commit is contained in:
Martin Atkins 2019-09-09 15:42:09 -07:00
parent 3327dee567
commit 0f5ab3bd56
15 changed files with 0 additions and 1812 deletions

View File

@ -1,24 +0,0 @@
package hclpack
import (
"github.com/agext/levenshtein"
)
// nameSuggestion tries to find a name from the given slice of suggested names
// that is close to the given name and returns it if found. If no suggestion
// is close enough, returns the empty string.
//
// The suggestions are tried in order, so earlier suggestions take precedence
// if the given string is similar to two or more suggestions.
//
// This function is intended to be used with a relatively-small number of
// suggestions. It's not optimized for hundreds or thousands of them.
func nameSuggestion(given string, suggestions []string) string {
for _, suggestion := range suggestions {
dist := levenshtein.Distance(given, suggestion, nil)
if dist < 3 { // threshold determined experimentally
return suggestion
}
}
return ""
}

View File

@ -1,14 +0,0 @@
// Package hclpack provides a straightforward representation of HCL block/body
// structure that can be easily serialized and deserialized for compact
// transmission (e.g. over a network) without transmitting the full source code.
//
// Expressions are retained in native syntax source form so that their
// evaluation can be delayed until a package structure is decoded by some
// other system that has enough information to populate the evaluation context.
//
// Packed structures retain source location information but do not retain
// actual source code. To make sense of source locations returned in diagnostics
// and via other APIs the caller must somehow gain access to the original source
// code that the packed representation was built from, which is a problem that
// must be solved somehow by the calling application.
package hclpack

View File

@ -1,129 +0,0 @@
package hclpack_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hclpack"
)
func Example_marshalJSON() {
src := `
service "example" {
priority = 2
platform {
os = "linux"
arch = "amd64"
}
process "web" {
exec = ["./webapp"]
}
process "worker" {
exec = ["./worker"]
}
}
`
body, diags := hclpack.PackNativeFile([]byte(src), "example.svc", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
fmt.Fprintf(os.Stderr, "Failed to parse: %s", diags.Error())
return
}
jb, err := body.MarshalJSON()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to marshal: %s", err)
return
}
// Normally the compact form is best, but we'll indent just for the sake
// of this example so the result is readable.
var buf bytes.Buffer
json.Indent(&buf, jb, "", " ")
os.Stdout.Write(buf.Bytes())
// Output:
// {
// "r": {
// "b": [
// {
// "h": [
// "service",
// "example"
// ],
// "b": {
// "a": {
// "priority": {
// "s": "2",
// "r": "ChAKDA4QDhA"
// }
// },
// "b": [
// {
// "h": [
// "platform"
// ],
// "b": {
// "a": {
// "arch": {
// "s": "\"amd64\"",
// "r": "IiwiJCYsKCo"
// },
// "os": {
// "s": "\"linux\"",
// "r": "FiAWGBogHB4"
// }
// },
// "r": "Li4"
// },
// "r": "EhQSFA"
// },
// {
// "h": [
// "process",
// "web"
// ],
// "b": {
// "a": {
// "exec": {
// "s": "[\"./webapp\"]",
// "r": "OEA4OjxAPD4"
// }
// },
// "r": "QkI"
// },
// "r": "MDYwMjQ2"
// },
// {
// "h": [
// "process",
// "worker"
// ],
// "b": {
// "a": {
// "exec": {
// "s": "[\"./worker\"]",
// "r": "TFRMTlBUUFI"
// }
// },
// "r": "VlY"
// },
// "r": "REpERkhK"
// }
// ],
// "r": "WFg"
// },
// "r": "AggCBAYI"
// }
// ],
// "r": "Wlo"
// },
// "s": [
// "example.svc"
// ],
// "p": "BAQEAA4OAAICABISAggMABAQAAYGAAICAggIABAQAgYKAAQEAAoKAAICAAoKAAICAgYGAAgIAAYGAAICAAoKAAICAgoKAggIAA4OAAICAAoKAgwQAAgIAAYGAAICABYWAgoKAggIAA4OAAICABAQAgwQAAgIAAYGAAICABYWAgoKAgYGAgQE"
// }
}

View File

@ -1,160 +0,0 @@
package hclpack
import (
"fmt"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcl/hclsyntax"
)
// Expression is an implementation of hcl.Expression in terms of some raw
// expression source code. The methods of this type will first parse the
// source code and then pass the call through to the real expression that
// is produced.
type Expression struct {
// Source is the raw source code of the expression, which should be parsed
// as the syntax specified by SourceType.
Source []byte
SourceType ExprSourceType
// Range_ and StartRange_ describe the physical extents of the expression
// in the original source code. SourceRange_ is its entire range while
// StartRange is just the tokens that introduce the expression type. For
// simple expression types, SourceRange and StartRange are identical.
Range_, StartRange_ hcl.Range
}
var _ hcl.Expression = (*Expression)(nil)
// Value implements the Value method of hcl.Expression but with the additional
// step of first parsing the expression source code. This implementation is
// unusual in that it can potentially return syntax errors, whereas other
// Value implementations usually work with already-parsed expressions.
func (e *Expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
expr, diags := e.Parse()
if diags.HasErrors() {
return cty.DynamicVal, diags
}
val, moreDiags := expr.Value(ctx)
diags = append(diags, moreDiags...)
return val, diags
}
// Variables implements the Variables method of hcl.Expression but with the
// additional step of first parsing the expression source code.
//
// Since this method cannot return errors, it will return a nil slice if
// parsing fails, indicating that no variables are present. This is okay in
// practice because a subsequent call to Value would fail with syntax errors
// regardless of what variables are in the context.
func (e *Expression) Variables() []hcl.Traversal {
expr, diags := e.Parse()
if diags.HasErrors() {
return nil
}
return expr.Variables()
}
// UnwrapExpression parses and returns the underlying expression, if possible.
//
// This is essentially the same as Parse but without the ability to return an
// error; it is here only to support the static analysis facilities in the
// main HCL package (ExprList, ExprMap, etc). If any error is encountered
// during parsing, the result is a static expression that always returns
// cty.DynamicVal.
//
// This function does not impose any further conversions on the underlying
// expression, so the result may still not be suitable for the static analysis
// functions, depending on the source type of the expression and thus what
// type of physical expression it becomes after decoding.
func (e *Expression) UnwrapExpression() hcl.Expression {
expr, diags := e.Parse()
if diags.HasErrors() {
return hcl.StaticExpr(cty.DynamicVal, e.Range_)
}
return expr
}
func (e *Expression) Range() hcl.Range {
return e.Range_
}
func (e *Expression) StartRange() hcl.Range {
return e.StartRange_
}
// Parse attempts to parse the source code of the receiving expression using
// its indicated source type, returning the expression if possible and any
// diagnostics produced during parsing.
func (e *Expression) Parse() (hcl.Expression, hcl.Diagnostics) {
switch e.SourceType {
case ExprNative:
return hclsyntax.ParseExpression(e.Source, e.Range_.Filename, e.Range_.Start)
case ExprTemplate:
return hclsyntax.ParseTemplate(e.Source, e.Range_.Filename, e.Range_.Start)
case ExprLiteralJSON:
ty, err := ctyjson.ImpliedType(e.Source)
if err != nil {
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Invalid JSON value",
Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err),
Subject: &e.Range_,
},
}
}
val, err := ctyjson.Unmarshal(e.Source, ty)
if err != nil {
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Invalid JSON value",
Detail: fmt.Sprintf("The JSON representation of this expression is invalid: %s.", err),
Subject: &e.Range_,
},
}
}
return hcl.StaticExpr(val, e.Range_), nil
default:
// This should never happen for a valid Expression.
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Invalid expression source type",
Detail: fmt.Sprintf("Packed version of this expression has an invalid source type %s. This is always a bug.", e.SourceType),
Subject: &e.Range_,
},
}
}
}
func (e *Expression) addRanges(rngs map[hcl.Range]struct{}) {
rngs[e.Range_] = struct{}{}
rngs[e.StartRange_] = struct{}{}
}
// ExprSourceType defines the syntax type used for an expression's source code,
// which is then used to select a suitable parser for it when evaluating.
type ExprSourceType rune
//go:generate stringer -type ExprSourceType
const (
// ExprNative indicates that an expression must be parsed as native
// expression syntax, with hclsyntax.ParseExpression.
ExprNative ExprSourceType = 'N'
// ExprTemplate indicates that an expression must be parsed as native
// template syntax, with hclsyntax.ParseTemplate.
ExprTemplate ExprSourceType = 'T'
// ExprLiteralJSON indicates that an expression must be parsed as JSON and
// treated literally, using cty/json. This can be used when populating
// literal attribute values from a non-HCL source.
ExprLiteralJSON ExprSourceType = 'L'
)

View File

@ -1,70 +0,0 @@
package hclpack
import (
"testing"
"github.com/hashicorp/hcl2/hcl"
"github.com/zclconf/go-cty/cty"
)
func TestExpressionValue(t *testing.T) {
tests := map[string]struct {
Expr *Expression
Ctx *hcl.EvalContext
Want cty.Value
}{
"simple literal expr": {
&Expression{
Source: []byte(`"hello"`),
SourceType: ExprNative,
},
nil,
cty.StringVal("hello"),
},
"simple literal template": {
&Expression{
Source: []byte(`hello ${5}`),
SourceType: ExprTemplate,
},
nil,
cty.StringVal("hello 5"),
},
"expr with variable": {
&Expression{
Source: []byte(`foo`),
SourceType: ExprNative,
},
&hcl.EvalContext{
Variables: map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
},
cty.StringVal("bar"),
},
"template with variable": {
&Expression{
Source: []byte(`foo ${foo}`),
SourceType: ExprTemplate,
},
&hcl.EvalContext{
Variables: map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
},
cty.StringVal("foo bar"),
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, diags := test.Expr.Value(test.Ctx)
for _, diag := range diags {
t.Errorf("unexpected diagnostic: %s", diag.Error())
}
if !test.Want.RawEquals(got) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}

View File

@ -1,24 +0,0 @@
// Code generated by "stringer -type ExprSourceType"; DO NOT EDIT.
package hclpack
import "strconv"
const (
_ExprSourceType_name_0 = "ExprLiteralJSON"
_ExprSourceType_name_1 = "ExprNative"
_ExprSourceType_name_2 = "ExprTemplate"
)
func (i ExprSourceType) String() string {
switch {
case i == 76:
return _ExprSourceType_name_0
case i == 78:
return _ExprSourceType_name_1
case i == 84:
return _ExprSourceType_name_2
default:
return "ExprSourceType(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@ -1,211 +0,0 @@
package hclpack
import (
"encoding/json"
"github.com/hashicorp/hcl2/hcl"
)
// MarshalJSON is an implementation of Marshaler from encoding/json, allowing
// bodies to be included in other types that are JSON-marshalable.
//
// The result of MarshalJSON is optimized for compactness rather than easy
// human consumption/editing. Use UnmarshalJSON to decode it.
func (b *Body) MarshalJSON() ([]byte, error) {
rngs := make(map[hcl.Range]struct{})
b.addRanges(rngs)
fns, posList, posMap := packPositions(rngs)
head := jsonHeader{
Body: b.forJSON(posMap),
Sources: fns,
Pos: posList,
}
return json.Marshal(&head)
}
func (b *Body) forJSON(pos map[string]map[hcl.Pos]posOfs) bodyJSON {
var ret bodyJSON
if len(b.Attributes) > 0 {
ret.Attrs = make(map[string]attrJSON, len(b.Attributes))
for name, attr := range b.Attributes {
ret.Attrs[name] = attr.forJSON(pos)
}
}
if len(b.ChildBlocks) > 0 {
ret.Blocks = make([]blockJSON, len(b.ChildBlocks))
for i, block := range b.ChildBlocks {
ret.Blocks[i] = block.forJSON(pos)
}
}
ret.Ranges = make(rangesPacked, 1)
ret.Ranges[0] = packRange(b.MissingItemRange_, pos)
return ret
}
func (a *Attribute) forJSON(pos map[string]map[hcl.Pos]posOfs) attrJSON {
var ret attrJSON
ret.Source = string(a.Expr.Source)
switch a.Expr.SourceType {
case ExprNative:
ret.Syntax = 0
case ExprTemplate:
ret.Syntax = 1
case ExprLiteralJSON:
ret.Syntax = 2
}
ret.Ranges = make(rangesPacked, 4)
ret.Ranges[0] = packRange(a.Range, pos)
ret.Ranges[1] = packRange(a.NameRange, pos)
ret.Ranges[2] = packRange(a.Expr.Range_, pos)
ret.Ranges[3] = packRange(a.Expr.StartRange_, pos)
return ret
}
func (b *Block) forJSON(pos map[string]map[hcl.Pos]posOfs) blockJSON {
var ret blockJSON
ret.Header = make([]string, len(b.Labels)+1)
ret.Header[0] = b.Type
copy(ret.Header[1:], b.Labels)
ret.Body = b.Body.forJSON(pos)
ret.Ranges = make(rangesPacked, 2+len(b.LabelRanges))
ret.Ranges[0] = packRange(b.DefRange, pos)
ret.Ranges[1] = packRange(b.TypeRange, pos)
for i, rng := range b.LabelRanges {
ret.Ranges[i+2] = packRange(rng, pos)
}
return ret
}
// UnmarshalJSON is an implementation of Unmarshaler from encoding/json,
// allowing bodies to be included in other types that are JSON-unmarshalable.
func (b *Body) UnmarshalJSON(data []byte) error {
var head jsonHeader
err := json.Unmarshal(data, &head)
if err != nil {
return err
}
fns := head.Sources
positions := head.Pos.Unpack()
*b = head.Body.decode(fns, positions)
return nil
}
type jsonHeader struct {
Body bodyJSON `json:"r"`
Sources []string `json:"s,omitempty"`
Pos positionsPacked `json:"p,omitempty"`
}
type bodyJSON struct {
// Files are the source filenames that were involved in
Attrs map[string]attrJSON `json:"a,omitempty"`
Blocks []blockJSON `json:"b,omitempty"`
// Ranges contains the MissingItemRange
Ranges rangesPacked `json:"r,omitempty"`
}
func (bj *bodyJSON) decode(fns []string, positions []position) Body {
var ret Body
if len(bj.Attrs) > 0 {
ret.Attributes = make(map[string]Attribute, len(bj.Attrs))
for name, aj := range bj.Attrs {
ret.Attributes[name] = aj.decode(fns, positions)
}
}
if len(bj.Blocks) > 0 {
ret.ChildBlocks = make([]Block, len(bj.Blocks))
for i, blj := range bj.Blocks {
ret.ChildBlocks[i] = blj.decode(fns, positions)
}
}
ret.MissingItemRange_ = bj.Ranges.UnpackIdx(fns, positions, 0)
return ret
}
type attrJSON struct {
// To keep things compact, in the JSON encoding we flatten the
// expression down into the attribute object, since overhead
// for attributes adds up in a complex config.
Source string `json:"s"`
Syntax int `json:"t,omitempty"` // omitted for 0=native
// Ranges contains the Range, NameRange, Expr.Range, Expr.StartRange
Ranges rangesPacked `json:"r,omitempty"`
}
func (aj *attrJSON) decode(fns []string, positions []position) Attribute {
var ret Attribute
ret.Expr.Source = []byte(aj.Source)
switch aj.Syntax {
case 0:
ret.Expr.SourceType = ExprNative
case 1:
ret.Expr.SourceType = ExprTemplate
case 2:
ret.Expr.SourceType = ExprLiteralJSON
}
ret.Range = aj.Ranges.UnpackIdx(fns, positions, 0)
ret.NameRange = aj.Ranges.UnpackIdx(fns, positions, 1)
ret.Expr.Range_ = aj.Ranges.UnpackIdx(fns, positions, 2)
ret.Expr.StartRange_ = aj.Ranges.UnpackIdx(fns, positions, 3)
if ret.Expr.StartRange_ == (hcl.Range{}) {
// If the start range wasn't present then we'll just use the Range
ret.Expr.StartRange_ = ret.Expr.Range_
}
return ret
}
type blockJSON struct {
// Header is the type followed by any labels. We flatten this here
// to keep the JSON encoding compact.
Header []string `json:"h"`
Body bodyJSON `json:"b,omitempty"`
// Ranges contains the DefRange followed by the TypeRange and then
// each of the label ranges in turn.
Ranges rangesPacked `json:"r,omitempty"`
}
func (blj *blockJSON) decode(fns []string, positions []position) Block {
var ret Block
if len(blj.Header) > 0 { // If the header is invalid then we'll end up with an empty type
ret.Type = blj.Header[0]
}
if len(blj.Header) > 1 {
ret.Labels = blj.Header[1:]
}
ret.Body = blj.Body.decode(fns, positions)
ret.DefRange = blj.Ranges.UnpackIdx(fns, positions, 0)
ret.TypeRange = blj.Ranges.UnpackIdx(fns, positions, 1)
if len(ret.Labels) > 0 {
ret.LabelRanges = make([]hcl.Range, len(ret.Labels))
for i := range ret.Labels {
ret.LabelRanges[i] = blj.Ranges.UnpackIdx(fns, positions, i+2)
}
}
return ret
}

View File

@ -1,47 +0,0 @@
package hclpack
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl2/hcl"
)
func TestJSONRoundTrip(t *testing.T) {
src := `
service "example" {
priority = 2
platform {
os = "linux"
arch = "amd64"
}
process "web" {
exec = ["./webapp"]
}
process "worker" {
exec = ["./worker"]
}
}
`
startBody, diags := PackNativeFile([]byte(src), "example.svc", hcl.Pos{Line: 1, Column: 1})
if diags.HasErrors() {
t.Fatalf("Failed to parse: %s", diags.Error())
}
jb, err := startBody.MarshalJSON()
if err != nil {
t.Fatalf("Failed to marshal: %s", err)
}
endBody := &Body{}
err = endBody.UnmarshalJSON(jb)
if err != nil {
t.Fatalf("Failed to unmarshal: %s", err)
}
if !cmp.Equal(startBody, endBody) {
t.Errorf("incorrect result\n%s", cmp.Diff(startBody, endBody))
}
}

View File

@ -1,58 +0,0 @@
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
}

View File

@ -1,166 +0,0 @@
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

@ -1,326 +0,0 @@
package hclpack
import (
"encoding/base64"
"sort"
"github.com/hashicorp/hcl2/hcl"
)
// positionsPacked is a delta-based representation of source positions
// that implements encoding.TextMarshaler and encoding.TextUnmarshaler using
// a compact variable-length quantity encoding to mimimize the overhead of
// storing source positions.
//
// Serializations of the other types in this package can refer to positions
// in a positionsPacked by index.
type positionsPacked []positionPacked
func (pp positionsPacked) MarshalBinary() ([]byte, error) {
lenInt := len(pp) * 4 // each positionPacked contains four ints, but we don't include the fileidx
// guess avg of ~1.25 bytes per int, in which case we'll avoid further allocation
buf := newVLQBuf(lenInt + (lenInt / 4))
var lastFileIdx int
for _, ppr := range pp {
// Rather than writing out the same file index over and over, we instead
// insert a ; delimiter each time it increases. Since it's common for
// for a body to be entirely in one file, this can lead to considerable
// savings in that case.
delims := ppr.FileIdx - lastFileIdx
lastFileIdx = ppr.FileIdx
for i := 0; i < delims; i++ {
buf = buf.AppendRawByte(';')
}
buf = buf.AppendInt(ppr.LineDelta)
buf = buf.AppendInt(ppr.ColumnDelta)
buf = buf.AppendInt(ppr.ByteDelta)
}
return buf.Bytes(), nil
}
func (pp positionsPacked) MarshalText() ([]byte, error) {
raw, err := pp.MarshalBinary()
if err != nil {
return nil, err
}
l := base64.RawStdEncoding.EncodedLen(len(raw))
ret := make([]byte, l)
base64.RawStdEncoding.Encode(ret, raw)
return ret, nil
}
func (pp *positionsPacked) UnmarshalBinary(data []byte) error {
buf := vlqBuf(data)
var ret positionsPacked
fileIdx := 0
for len(buf) > 0 {
if buf[0] == ';' {
// Starting a new file, then.
fileIdx++
buf = buf[1:]
continue
}
var ppr positionPacked
var err error
ppr.FileIdx = fileIdx
ppr.LineDelta, buf, err = buf.ReadInt()
if err != nil {
return err
}
ppr.ColumnDelta, buf, err = buf.ReadInt()
if err != nil {
return err
}
ppr.ByteDelta, buf, err = buf.ReadInt()
if err != nil {
return err
}
ret = append(ret, ppr)
}
*pp = ret
return nil
}
func (pp *positionsPacked) UnmarshalText(data []byte) error {
maxL := base64.RawStdEncoding.DecodedLen(len(data))
into := make([]byte, maxL)
realL, err := base64.RawStdEncoding.Decode(into, data)
if err != nil {
return err
}
return pp.UnmarshalBinary(into[:realL])
}
type position struct {
FileIdx int
Pos hcl.Pos
}
func (pp positionsPacked) Unpack() []position {
ret := make([]position, len(pp))
var accPos hcl.Pos
var accFileIdx int
for i, relPos := range pp {
if relPos.FileIdx != accFileIdx {
accPos = hcl.Pos{} // reset base position for each new file
accFileIdx = pp[i].FileIdx
}
if relPos.LineDelta > 0 {
accPos.Column = 0 // reset column position for each new line
}
accPos.Line += relPos.LineDelta
accPos.Column += relPos.ColumnDelta
accPos.Byte += relPos.ByteDelta
ret[i] = position{
FileIdx: relPos.FileIdx,
Pos: accPos,
}
}
return ret
}
type positionPacked struct {
FileIdx int
LineDelta, ColumnDelta, ByteDelta int
}
func (pp positionsPacked) Len() int {
return len(pp)
}
func (pp positionsPacked) Less(i, j int) bool {
return pp[i].FileIdx < pp[j].FileIdx
}
func (pp positionsPacked) Swap(i, j int) {
pp[i], pp[j] = pp[j], pp[i]
}
// posOfs is an index into a positionsPacked. The zero value of this type
// represents the absense of a position.
type posOfs int
func newPosOffs(idx int) posOfs {
return posOfs(idx + 1)
}
func (o posOfs) Index() int {
return int(o - 1)
}
// rangePacked is a range represented as two indexes into a positionsPacked.
// This implements encoding.TextMarshaler and encoding.TextUnmarshaler using
// a compact variable-length quantity encoding.
type rangePacked struct {
Start posOfs
End posOfs
}
func packRange(rng hcl.Range, pos map[string]map[hcl.Pos]posOfs) rangePacked {
return rangePacked{
Start: pos[rng.Filename][rng.Start],
End: pos[rng.Filename][rng.End],
}
}
func (rp rangePacked) Unpack(fns []string, poss []position) hcl.Range {
startIdx := rp.Start.Index()
endIdx := rp.End.Index()
if startIdx < 0 && startIdx >= len(poss) {
return hcl.Range{} // out of bounds, so invalid
}
if endIdx < 0 && endIdx >= len(poss) {
return hcl.Range{} // out of bounds, so invalid
}
startPos := poss[startIdx]
endPos := poss[endIdx]
fnIdx := startPos.FileIdx
var fn string
if fnIdx >= 0 && fnIdx < len(fns) {
fn = fns[fnIdx]
}
return hcl.Range{
Filename: fn,
Start: startPos.Pos,
End: endPos.Pos,
}
}
// rangesPacked represents a sequence of ranges, packed compactly into a single
// string during marshaling.
type rangesPacked []rangePacked
func (rp rangesPacked) MarshalBinary() ([]byte, error) {
lenInt := len(rp) * 2 // each positionPacked contains two ints
// guess avg of ~1.25 bytes per int, in which case we'll avoid further allocation
buf := newVLQBuf(lenInt + (lenInt / 4))
for _, rpr := range rp {
buf = buf.AppendInt(int(rpr.Start)) // intentionally storing these as 1-based offsets
buf = buf.AppendInt(int(rpr.End))
}
return buf.Bytes(), nil
}
func (rp rangesPacked) MarshalText() ([]byte, error) {
raw, err := rp.MarshalBinary()
if err != nil {
return nil, err
}
l := base64.RawStdEncoding.EncodedLen(len(raw))
ret := make([]byte, l)
base64.RawStdEncoding.Encode(ret, raw)
return ret, nil
}
func (rp *rangesPacked) UnmarshalBinary(data []byte) error {
buf := vlqBuf(data)
var ret rangesPacked
for len(buf) > 0 {
var startInt, endInt int
var err error
startInt, buf, err = buf.ReadInt()
if err != nil {
return err
}
endInt, buf, err = buf.ReadInt()
if err != nil {
return err
}
ret = append(ret, rangePacked{
Start: posOfs(startInt), // these are stored as 1-based offsets, so safe to convert directly
End: posOfs(endInt),
})
}
*rp = ret
return nil
}
func (rp *rangesPacked) UnmarshalText(data []byte) error {
maxL := base64.RawStdEncoding.DecodedLen(len(data))
into := make([]byte, maxL)
realL, err := base64.RawStdEncoding.Decode(into, data)
if err != nil {
return err
}
return rp.UnmarshalBinary(into[:realL])
}
func (rps rangesPacked) UnpackIdx(fns []string, poss []position, idx int) hcl.Range {
if idx < 0 || idx >= len(rps) {
return hcl.Range{} // out of bounds, so invalid
}
return rps[idx].Unpack(fns, poss)
}
// packPositions will find the distinct positions from the given ranges
// and then pack them into a positionsPacked, along with a lookup table to find
// the encoded offset of each distinct position.
func packPositions(rngs map[hcl.Range]struct{}) (fns []string, poss positionsPacked, posMap map[string]map[hcl.Pos]posOfs) {
const noOfs = posOfs(0)
posByFile := make(map[string][]hcl.Pos)
for rng := range rngs {
fn := rng.Filename
posByFile[fn] = append(posByFile[fn], rng.Start)
posByFile[fn] = append(posByFile[fn], rng.End)
}
fns = make([]string, 0, len(posByFile))
for fn := range posByFile {
fns = append(fns, fn)
}
sort.Strings(fns)
var retPos positionsPacked
posMap = make(map[string]map[hcl.Pos]posOfs)
for fileIdx, fn := range fns {
poss := posByFile[fn]
sort.Sort(sortPositions(poss))
var prev hcl.Pos
for _, pos := range poss {
if _, exists := posMap[fn][pos]; exists {
continue
}
ofs := newPosOffs(len(retPos))
if pos.Line != prev.Line {
// Column indices start from zero for each new line.
prev.Column = 0
}
retPos = append(retPos, positionPacked{
FileIdx: fileIdx,
LineDelta: pos.Line - prev.Line,
ColumnDelta: pos.Column - prev.Column,
ByteDelta: pos.Byte - prev.Byte,
})
if posMap[fn] == nil {
posMap[fn] = make(map[hcl.Pos]posOfs)
}
posMap[fn][pos] = ofs
prev = pos
}
}
return fns, retPos, posMap
}
type sortPositions []hcl.Pos
func (sp sortPositions) Len() int {
return len(sp)
}
func (sp sortPositions) Less(i, j int) bool {
return sp[i].Byte < sp[j].Byte
}
func (sp sortPositions) Swap(i, j int) {
sp[i], sp[j] = sp[j], sp[i]
}

View File

@ -1,30 +0,0 @@
package hclpack
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestBinaryRoundTrip(t *testing.T) {
startPacked := positionsPacked{
{FileIdx: 0, LineDelta: 1, ColumnDelta: 2, ByteDelta: 3},
{FileIdx: 1, LineDelta: 2, ColumnDelta: 3, ByteDelta: 4},
{FileIdx: 2, LineDelta: 3, ColumnDelta: 4, ByteDelta: 5},
}
b, err := startPacked.MarshalBinary()
if err != nil {
t.Fatalf("Failed to marshal: %s", err)
}
var endPacked positionsPacked
err = endPacked.UnmarshalBinary(b)
if err != nil {
t.Fatalf("Failed to unmarshal: %s", err)
}
if !cmp.Equal(startPacked, endPacked) {
t.Errorf("Incorrect result\n%s", cmp.Diff(startPacked, endPacked))
}
}

View File

@ -1,297 +0,0 @@
package hclpack
import (
"fmt"
"github.com/hashicorp/hcl2/hcl"
)
// Body is an implementation of hcl.Body.
type Body struct {
Attributes map[string]Attribute
ChildBlocks []Block
MissingItemRange_ hcl.Range
}
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)
}
// PartialContent is an implementation of the method of the same name on hcl.Body.
//
// 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{
MissingItemRange_: b.MissingItemRange_,
}
content, diags := b.content(schema, remain)
return content, remain, diags
}
func (b *Body) content(schema *hcl.BodySchema, remain *Body) (*hcl.BodyContent, hcl.Diagnostics) {
if b == nil {
b = &Body{} // We'll treat a nil body like an empty one, for convenience
}
var diags hcl.Diagnostics
var attrs map[string]*hcl.Attribute
var attrUsed map[string]struct{}
if len(b.Attributes) > 0 {
attrs = make(map[string]*hcl.Attribute, len(b.Attributes))
attrUsed = make(map[string]struct{}, len(b.Attributes))
}
for _, attrS := range schema.Attributes {
name := attrS.Name
attr, exists := b.Attributes[name]
if !exists {
if attrS.Required {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Missing required argument",
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
Subject: &b.MissingItemRange_,
})
}
continue
}
attrs[name] = attr.asHCLAttribute(name)
attrUsed[name] = struct{}{}
}
for name, attr := range b.Attributes {
if _, used := attrUsed[name]; used {
continue
}
if remain != nil {
remain.setAttribute(name, attr)
continue
}
var suggestions []string
for _, attrS := range schema.Attributes {
if _, defined := attrs[name]; defined {
continue
}
suggestions = append(suggestions, attrS.Name)
}
suggestion := nameSuggestion(name, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
} else {
// Is there a block of the same name?
for _, blockS := range schema.Blocks {
if blockS.Type == name {
suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name)
break
}
}
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported argument",
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
Subject: attr.NameRange.Ptr(),
})
}
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
for _, blockS := range schema.Blocks {
blocksWanted[blockS.Type] = blockS
}
var blocks []*hcl.Block
for _, block := range b.ChildBlocks {
// Redeclare block on stack so the pointer to the body is set on the
// correct block. https://github.com/hashicorp/hcl2/issues/72
block := block
blockTy := block.Type
blockS, wanted := blocksWanted[blockTy]
if !wanted {
if remain != nil {
remain.appendBlock(block)
continue
}
var suggestions []string
for _, blockS := range schema.Blocks {
suggestions = append(suggestions, blockS.Type)
}
suggestion := nameSuggestion(blockTy, suggestions)
if suggestion != "" {
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
} else {
// Is there an attribute of the same name?
for _, attrS := range schema.Attributes {
if attrS.Name == blockTy {
suggestion = fmt.Sprintf(" Did you mean to define argument %q?", blockTy)
break
}
}
}
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unsupported block type",
Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
Subject: &block.TypeRange,
})
continue
}
if len(block.Labels) != len(blockS.LabelNames) {
if len(blockS.LabelNames) == 0 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", blockTy),
Detail: fmt.Sprintf(
"No labels are expected for %s blocks.", blockTy,
),
Subject: &block.DefRange,
Context: &block.DefRange,
})
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Wrong label count for %s", blockTy),
Detail: fmt.Sprintf(
"%s blocks expect %d label(s), but got %d.",
blockTy, len(blockS.LabelNames), len(block.Labels),
),
Subject: &block.DefRange,
Context: &block.DefRange,
})
}
continue
}
blocks = append(blocks, block.asHCLBlock())
}
return &hcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.MissingItemRange_,
}, diags
}
// JustAttributes is an implementation of the method of the same name on hcl.Body.
func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
var diags hcl.Diagnostics
if len(b.ChildBlocks) > 0 {
for _, block := range b.ChildBlocks {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Unexpected %s block", block.Type),
Detail: "Blocks are not allowed here.",
Context: &block.TypeRange,
})
}
// We'll continue processing anyway, and return any attributes we find
// so that the caller can do careful partial analysis.
}
if len(b.Attributes) == 0 {
return nil, diags
}
ret := make(hcl.Attributes, len(b.Attributes))
for n, a := range b.Attributes {
ret[n] = a.asHCLAttribute(n)
}
return ret, diags
}
// MissingItemRange is an implementation of the method of the same name on hcl.Body.
func (b *Body) MissingItemRange() hcl.Range {
return b.MissingItemRange_
}
func (b *Body) setAttribute(name string, attr Attribute) {
if b.Attributes == nil {
b.Attributes = make(map[string]Attribute)
}
b.Attributes[name] = attr
}
func (b *Body) appendBlock(block Block) {
b.ChildBlocks = append(b.ChildBlocks, block)
}
func (b *Body) addRanges(rngs map[hcl.Range]struct{}) {
rngs[b.MissingItemRange_] = struct{}{}
for _, attr := range b.Attributes {
attr.addRanges(rngs)
}
for _, block := range b.ChildBlocks {
block.addRanges(rngs)
}
}
// Block represents a nested block within a body.
type Block struct {
Type string
Labels []string
Body Body
DefRange, TypeRange hcl.Range
LabelRanges []hcl.Range
}
func (b *Block) asHCLBlock() *hcl.Block {
return &hcl.Block{
Type: b.Type,
Labels: b.Labels,
Body: &b.Body,
TypeRange: b.TypeRange,
DefRange: b.DefRange,
LabelRanges: b.LabelRanges,
}
}
func (b *Block) addRanges(rngs map[hcl.Range]struct{}) {
rngs[b.DefRange] = struct{}{}
rngs[b.TypeRange] = struct{}{}
for _, rng := range b.LabelRanges {
rngs[rng] = struct{}{}
}
b.Body.addRanges(rngs)
}
// Attribute represents an attribute definition within a body.
type Attribute struct {
Expr Expression
Range, NameRange hcl.Range
}
func (a *Attribute) asHCLAttribute(name string) *hcl.Attribute {
return &hcl.Attribute{
Name: name,
Expr: &a.Expr,
Range: a.Range,
NameRange: a.NameRange,
}
}
func (a *Attribute) addRanges(rngs map[hcl.Range]struct{}) {
rngs[a.Range] = struct{}{}
rngs[a.NameRange] = struct{}{}
a.Expr.addRanges(rngs)
}

View File

@ -1,206 +0,0 @@
package hclpack
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl2/hcl"
)
func TestBodyContent(t *testing.T) {
tests := map[string]struct {
Body *Body
Schema *hcl.BodySchema
Want *hcl.BodyContent
}{
"empty": {
&Body{},
&hcl.BodySchema{},
&hcl.BodyContent{},
},
"nil": {
nil,
&hcl.BodySchema{},
&hcl.BodyContent{},
},
"attribute": {
&Body{
Attributes: map[string]Attribute{
"foo": {
Expr: Expression{
Source: []byte(`"hello"`),
SourceType: ExprNative,
},
},
},
},
&hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "foo", Required: true},
{Name: "bar", Required: false},
},
},
&hcl.BodyContent{
Attributes: hcl.Attributes{
"foo": {
Name: "foo",
Expr: &Expression{
Source: []byte(`"hello"`),
SourceType: ExprNative,
},
},
},
},
},
"block": {
&Body{
ChildBlocks: []Block{
{
Type: "foo",
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: "foo"},
},
},
&hcl.BodyContent{
Blocks: hcl.Blocks{
{
Type: "foo",
Body: &Body{},
},
},
},
},
"block attributes": {
&Body{
ChildBlocks: []Block{
{
Type: "foo",
Body: Body{
Attributes: map[string]Attribute{
"bar": {
Expr: Expression{
Source: []byte(`"hello"`),
SourceType: ExprNative,
},
},
},
},
},
{
Type: "foo",
Body: Body{
Attributes: map[string]Attribute{
"bar": {
Expr: Expression{
Source: []byte(`"world"`),
SourceType: ExprNative,
},
},
},
},
},
},
},
&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{Type: "foo"},
},
},
&hcl.BodyContent{
Blocks: hcl.Blocks{
{
Type: "foo",
Body: &Body{
Attributes: map[string]Attribute{
"bar": {
Expr: Expression{
Source: []byte(`"hello"`),
SourceType: ExprNative,
},
},
},
},
},
{
Type: "foo",
Body: &Body{
Attributes: map[string]Attribute{
"bar": {
Expr: Expression{
Source: []byte(`"world"`),
SourceType: ExprNative,
},
},
},
},
},
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, diags := test.Body.Content(test.Schema)
for _, diag := range diags {
t.Errorf("unexpected diagnostic: %s", diag.Error())
}
if !cmp.Equal(test.Want, got) {
bytesAsString := func(s []byte) string {
return string(s)
}
t.Errorf("wrong result\n%s", cmp.Diff(
test.Want, got,
cmp.Transformer("bytesAsString", bytesAsString),
))
}
})
}
}
func TestBodyPartialContent(t *testing.T) {
tests := map[string]struct {
Body *Body
Schema *hcl.BodySchema
WantRemain hcl.Body
}{
"missing range": {
&Body{
MissingItemRange_: hcl.Range{
Filename: "file.hcl",
Start: hcl.Pos{Line: 3, Column: 2},
End: hcl.Pos{Line: 3, Column: 2},
},
},
&hcl.BodySchema{},
&Body{
MissingItemRange_: hcl.Range{
Filename: "file.hcl",
Start: hcl.Pos{Line: 3, Column: 2},
End: hcl.Pos{Line: 3, Column: 2},
},
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
_, gotRemain, diags := test.Body.PartialContent(test.Schema)
for _, diag := range diags {
t.Errorf("unexpected diagnostic: %s", diag.Error())
}
if !cmp.Equal(test.WantRemain, gotRemain) {
t.Errorf("wrong remaining result\n%s", cmp.Diff(test.WantRemain, gotRemain))
}
})
}
}

View File

@ -1,50 +0,0 @@
package hclpack
import (
"errors"
"github.com/bsm/go-vlq"
)
type vlqBuf []byte
var vlqSpace [vlq.MaxLen64]byte
func newVLQBuf(byteCap int) vlqBuf {
return make(vlqBuf, 0, byteCap)
}
func (b vlqBuf) AppendInt(i int) vlqBuf {
spc := cap(b) - len(b)
if spc < len(vlqSpace) {
b = append(b, vlqSpace[:]...)
b = b[:len(b)-len(vlqSpace)]
}
into := b[len(b):cap(b)]
l := vlq.PutInt(into, int64(i))
b = b[:len(b)+l]
return b
}
func (b vlqBuf) ReadInt() (int, vlqBuf, error) {
v, adv := vlq.Int([]byte(b))
if adv <= 0 {
if adv == 0 {
return 0, b, errors.New("missing expected VLQ value")
} else {
return 0, b, errors.New("invalid VLQ value")
}
}
if int64(int(v)) != v {
return 0, b, errors.New("VLQ value too big for integer on this platform")
}
return int(v), b[adv:], nil
}
func (b vlqBuf) AppendRawByte(by byte) vlqBuf {
return append(b, by)
}
func (b vlqBuf) Bytes() []byte {
return []byte(b)
}