ext/include: Remove

This experimental extension is not ready to be included in a release. It
ought to be reworked so that "include" blocks get replaced with what they
include _in-place_, preserving the relative ordering of blocks.

However, there is no application making use of this yet and so we'll defer
that work until there's a real use-case to evaluate it with.
This commit is contained in:
Martin Atkins 2019-09-10 10:49:36 -07:00
parent a0458905ff
commit 691f9eea1a
6 changed files with 0 additions and 325 deletions

View File

@ -1,12 +0,0 @@
// Package include implements a HCL extension that allows inclusion of
// one HCL body into another using blocks of type "include", with the following
// structure:
//
// include {
// path = "./foo.hcl"
// }
//
// The processing of the given path is delegated to the calling application,
// allowing it to decide how to interpret the path and which syntaxes to
// support for referenced files.
package include

View File

@ -1,52 +0,0 @@
package include
import (
"path/filepath"
"strings"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse"
)
// FileResolver creates and returns a Resolver that interprets include paths
// as filesystem paths relative to the calling configuration file.
//
// When an include is requested, the source filename of the calling config
// file is first interpreted relative to the given basePath, and then the
// path given in configuration is interpreted relative to the resulting
// absolute caller configuration directory.
//
// This resolver assumes that all calling bodies are loaded from local files
// and that the paths to these files were correctly provided to the parser,
// either absolute or relative to the given basePath.
//
// If the path given in configuration ends with ".json" then the referenced
// file is interpreted as JSON. Otherwise, it is interpreted as HCL native
// syntax.
func FileResolver(baseDir string, parser *hclparse.Parser) Resolver {
return &fileResolver{
BaseDir: baseDir,
Parser: parser,
}
}
type fileResolver struct {
BaseDir string
Parser *hclparse.Parser
}
func (r fileResolver) ResolveBodyPath(path string, refRange hcl.Range) (hcl.Body, hcl.Diagnostics) {
callerFile := filepath.Join(r.BaseDir, refRange.Filename)
callerDir := filepath.Dir(callerFile)
targetFile := filepath.Join(callerDir, path)
var f *hcl.File
var diags hcl.Diagnostics
if strings.HasSuffix(targetFile, ".json") {
f, diags = r.Parser.ParseJSONFile(targetFile)
} else {
f, diags = r.Parser.ParseHCLFile(targetFile)
}
return f.Body, diags
}

View File

@ -1,29 +0,0 @@
package include
import (
"fmt"
"github.com/hashicorp/hcl/v2"
)
// MapResolver returns a Resolver that consults the given map for preloaded
// bodies (the values) associated with static include paths (the keys).
//
// An error diagnostic is returned if a path is requested that does not appear
// as a key in the given map.
func MapResolver(m map[string]hcl.Body) Resolver {
return ResolverFunc(func(path string, refRange hcl.Range) (hcl.Body, hcl.Diagnostics) {
if body, ok := m[path]; ok {
return body, nil
}
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Invalid include path",
Detail: fmt.Sprintf("The include path %q is not recognized.", path),
Subject: &refRange,
},
}
})
}

View File

@ -1,28 +0,0 @@
package include
import (
"github.com/hashicorp/hcl/v2"
)
// A Resolver maps an include path (an arbitrary string, but usually something
// filepath-like) to a hcl.Body.
//
// The parameter "refRange" is the source range of the expression in the calling
// body that provided the given path, for use in generating "invalid path"-type
// diagnostics.
//
// If the returned body is nil, it will be ignored.
//
// Any returned diagnostics will be emitted when content is requested from the
// final composed body (after all includes have been dealt with).
type Resolver interface {
ResolveBodyPath(path string, refRange hcl.Range) (hcl.Body, hcl.Diagnostics)
}
// ResolverFunc is a function type that implements Resolver.
type ResolverFunc func(path string, refRange hcl.Range) (hcl.Body, hcl.Diagnostics)
// ResolveBodyPath is an implementation of Resolver.ResolveBodyPath.
func (f ResolverFunc) ResolveBodyPath(path string, refRange hcl.Range) (hcl.Body, hcl.Diagnostics) {
return f(path, refRange)
}

View File

@ -1,92 +0,0 @@
package include
import (
"github.com/hashicorp/hcl/v2/ext/transform"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2"
)
// Transformer builds a transformer that finds any "include" blocks in a body
// and produces a merged body that contains the original content plus the
// content of the other bodies referenced by the include blocks.
//
// blockType specifies the type of block to interpret. The conventional type name
// is "include".
//
// ctx provides an evaluation context for the path expressions in include blocks.
// If nil, path expressions may not reference variables nor functions.
//
// The given resolver is used to translate path strings (after expression
// evaluation) into bodies. FileResolver returns a reasonable implementation for
// applications that read configuration files from local disk.
//
// The returned Transformer can either be used directly to process includes
// in a shallow fashion on a single body, or it can be used with
// transform.Deep (from the sibling transform package) to allow includes
// at all levels of a nested block structure:
//
// transformer = include.Transformer("include", nil, include.FileResolver(".", parser))
// body = transform.Deep(body, transformer)
// // "body" will now have includes resolved in its own content and that
// // of any descendent blocks.
//
func Transformer(blockType string, ctx *hcl.EvalContext, resolver Resolver) transform.Transformer {
return &transformer{
Schema: &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: blockType,
},
},
},
Ctx: ctx,
Resolver: resolver,
}
}
type transformer struct {
Schema *hcl.BodySchema
Ctx *hcl.EvalContext
Resolver Resolver
}
func (t *transformer) TransformBody(in hcl.Body) hcl.Body {
content, remain, diags := in.PartialContent(t.Schema)
if content == nil || len(content.Blocks) == 0 {
// Nothing to do!
return transform.BodyWithDiagnostics(remain, diags)
}
bodies := make([]hcl.Body, 1, len(content.Blocks)+1)
bodies[0] = remain // content in "remain" takes priority over includes
for _, block := range content.Blocks {
incContent, incDiags := block.Body.Content(includeBlockSchema)
diags = append(diags, incDiags...)
if incDiags.HasErrors() {
continue
}
pathExpr := incContent.Attributes["path"].Expr
var path string
incDiags = gohcl.DecodeExpression(pathExpr, t.Ctx, &path)
diags = append(diags, incDiags...)
if incDiags.HasErrors() {
continue
}
incBody, incDiags := t.Resolver.ResolveBodyPath(path, pathExpr.Range())
bodies = append(bodies, transform.BodyWithDiagnostics(incBody, incDiags))
}
return hcl.MergeBodies(bodies)
}
var includeBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: "path",
Required: true,
},
},
}

View File

@ -1,112 +0,0 @@
package include
import (
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hcltest"
"github.com/zclconf/go-cty/cty"
)
func TestTransformer(t *testing.T) {
caller := hcltest.MockBody(&hcl.BodyContent{
Blocks: hcl.Blocks{
{
Type: "include",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"path": hcltest.MockExprVariable("var_path"),
}),
}),
},
{
Type: "include",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"path": hcltest.MockExprLiteral(cty.StringVal("include2")),
}),
}),
},
{
Type: "foo",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"from": hcltest.MockExprLiteral(cty.StringVal("caller")),
}),
}),
},
},
})
resolver := MapResolver(map[string]hcl.Body{
"include1": hcltest.MockBody(&hcl.BodyContent{
Blocks: hcl.Blocks{
{
Type: "foo",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"from": hcltest.MockExprLiteral(cty.StringVal("include1")),
}),
}),
},
},
}),
"include2": hcltest.MockBody(&hcl.BodyContent{
Blocks: hcl.Blocks{
{
Type: "foo",
Body: hcltest.MockBody(&hcl.BodyContent{
Attributes: hcltest.MockAttrs(map[string]hcl.Expression{
"from": hcltest.MockExprLiteral(cty.StringVal("include2")),
}),
}),
},
},
}),
})
ctx := &hcl.EvalContext{
Variables: map[string]cty.Value{
"var_path": cty.StringVal("include1"),
},
}
transformer := Transformer("include", ctx, resolver)
merged := transformer.TransformBody(caller)
type foo struct {
From string `hcl:"from,attr"`
}
type result struct {
Foos []foo `hcl:"foo,block"`
}
var got result
diags := gohcl.DecodeBody(merged, nil, &got)
if len(diags) != 0 {
t.Errorf("unexpected diags")
for _, diag := range diags {
t.Logf("- %s", diag)
}
}
want := result{
Foos: []foo{
{
From: "caller",
},
{
From: "include1",
},
{
From: "include2",
},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want))
}
}