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:
parent
3327dee567
commit
0f5ab3bd56
@ -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 ""
|
||||
}
|
@ -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
|
@ -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"
|
||||
// }
|
||||
}
|
@ -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'
|
||||
)
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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) + ")"
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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),
|
||||
))
|
||||
}
|
||||
}
|
@ -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]
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user