hclwrite

package
v0.0.0-...-fb75b32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2019 License: MPL-2.0 Imports: 11 Imported by: 31

Documentation

Overview

Package hclwrite deals with the problem of generating HCL configuration and of making specific surgical changes to existing HCL configurations.

It operates at a different level of abstraction than the main HCL parser and AST, since details such as the placement of comments and newlines are preserved when unchanged.

The hclwrite API follows a similar principle to XML/HTML DOM, allowing nodes to be read out, created and inserted, etc. Nodes represent syntax constructs rather than semantic concepts.

Example (GenerateFromScratch)
package main

import (
	"fmt"

	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/hcl2/hclwrite"
	"github.com/zclconf/go-cty/cty"
)

func main() {
	f := hclwrite.NewEmptyFile()
	rootBody := f.Body()
	rootBody.SetAttributeValue("string", cty.StringVal("bar")) // this is overwritten later
	rootBody.AppendNewline()
	rootBody.SetAttributeValue("object", cty.ObjectVal(map[string]cty.Value{
		"foo": cty.StringVal("foo"),
		"bar": cty.NumberIntVal(5),
		"baz": cty.True,
	}))
	rootBody.SetAttributeValue("string", cty.StringVal("foo"))
	rootBody.SetAttributeValue("bool", cty.False)
	rootBody.SetAttributeTraversal("path", hcl.Traversal{
		hcl.TraverseRoot{
			Name: "env",
		},
		hcl.TraverseAttr{
			Name: "PATH",
		},
	})
	rootBody.AppendNewline()
	fooBlock := rootBody.AppendNewBlock("foo", nil)
	fooBody := fooBlock.Body()
	rootBody.AppendNewBlock("empty", nil)
	rootBody.AppendNewline()
	barBlock := rootBody.AppendNewBlock("bar", []string{"a", "b"})
	barBody := barBlock.Body()

	fooBody.SetAttributeValue("hello", cty.StringVal("world"))

	bazBlock := barBody.AppendNewBlock("baz", nil)
	bazBody := bazBlock.Body()
	bazBody.SetAttributeValue("foo", cty.NumberIntVal(10))
	bazBody.SetAttributeValue("beep", cty.StringVal("boop"))
	bazBody.SetAttributeValue("baz", cty.ListValEmpty(cty.String))

	fmt.Printf("%s", f.Bytes())
}
Output:

string = "foo"

object = { bar = 5, baz = true, foo = "foo" }
bool   = false
path   = env.PATH

foo {
  hello = "world"
}
empty {
}

bar "a" "b" {
  baz {
    foo  = 10
    beep = "boop"
    baz  = []
  }
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Format

func Format(src []byte) []byte

Format takes source code and performs simple whitespace changes to transform it to a canonical layout style.

Format skips constructing an AST and works directly with tokens, so it is less expensive than formatting via the AST for situations where no other changes will be made. It also ignores syntax errors and can thus be applied to partial source code, although the result in that case may not be desirable.

Types

type Attribute

type Attribute struct {
	// contains filtered or unexported fields
}

func (*Attribute) BuildTokens

func (it *Attribute) BuildTokens(to Tokens) Tokens

func (*Attribute) Expr

func (a *Attribute) Expr() *Expression

type Block

type Block struct {
	// contains filtered or unexported fields
}

func NewBlock

func NewBlock(typeName string, labels []string) *Block

NewBlock constructs a new, empty block with the given type name and labels.

func (*Block) Body

func (b *Block) Body() *Body

Body returns the body that represents the content of the receiving block.

Appending to or otherwise modifying this body will make changes to the tokens that are generated between the blocks open and close braces.

func (*Block) BuildTokens

func (it *Block) BuildTokens(to Tokens) Tokens

type Body

type Body struct {
	// contains filtered or unexported fields
}

func (*Body) AppendBlock

func (b *Body) AppendBlock(block *Block) *Block

AppendBlock appends an existing block (which must not be already attached to a body) to the end of the receiving body.

func (*Body) AppendNewBlock

func (b *Body) AppendNewBlock(typeName string, labels []string) *Block

AppendNewBlock appends a new nested block to the end of the receiving body with the given type name and labels.

func (*Body) AppendNewline

func (b *Body) AppendNewline()

AppendNewline appends a newline token to th end of the receiving body, which generally serves as a separator between different sets of body contents.

func (*Body) AppendUnstructuredTokens

func (b *Body) AppendUnstructuredTokens(ts Tokens)

func (*Body) Attributes

func (b *Body) Attributes() map[string]*Attribute

Attributes returns a new map of all of the attributes in the body, with the attribute names as the keys.

func (*Body) Blocks

func (b *Body) Blocks() []*Block

Blocks returns a new slice of all the blocks in the body.

func (*Body) BuildTokens

func (it *Body) BuildTokens(to Tokens) Tokens

func (*Body) Clear

func (b *Body) Clear()

Clear removes all of the items from the body, making it empty.

func (*Body) GetAttribute

func (b *Body) GetAttribute(name string) *Attribute

GetAttribute returns the attribute from the body that has the given name, or returns nil if there is currently no matching attribute.

func (*Body) SetAttributeTraversal

func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute

SetAttributeTraversal either replaces the expression of an existing attribute of the given name or adds a new attribute definition to the end of the body.

The new expression is given as a hcl.Traversal, which must be an absolute traversal. To set a literal value, use SetAttributeValue.

The return value is the attribute that was either modified in-place or created.

func (*Body) SetAttributeValue

func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute

SetAttributeValue either replaces the expression of an existing attribute of the given name or adds a new attribute definition to the end of the block.

The value is given as a cty.Value, and must therefore be a literal. To set a variable reference or other traversal, use SetAttributeTraversal.

The return value is the attribute that was either modified in-place or created.

type Expression

type Expression struct {
	// contains filtered or unexported fields
}

func NewExpressionAbsTraversal

func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression

NewExpressionAbsTraversal constructs an expression that represents the given traversal, which must be absolute or this function will panic.

func NewExpressionLiteral

func NewExpressionLiteral(val cty.Value) *Expression

NewExpressionLiteral constructs an an expression that represents the given literal value.

Since an unknown value cannot be represented in source code, this function will panic if the given value is unknown or contains a nested unknown value. Use val.IsWhollyKnown before calling to be sure.

HCL native syntax does not directly represent lists, maps, and sets, and instead relies on the automatic conversions to those collection types from either list or tuple constructor syntax. Therefore converting collection values to source code and re-reading them will lose type information, and the reader must provide a suitable type at decode time to recover the original value.

func (*Expression) BuildTokens

func (it *Expression) BuildTokens(to Tokens) Tokens

func (*Expression) RenameVariablePrefix

func (e *Expression) RenameVariablePrefix(search, replacement []string)

RenameVariablePrefix examines each of the absolute traversals in the receiving expression to see if they have the given sequence of names as a prefix prefix. If so, they are updated in place to have the given replacement names instead of that prefix.

This can be used to implement symbol renaming. The calling application can visit all relevant expressions in its input and apply the same renaming to implement a global symbol rename.

The search and replacement traversals must be the same length, or this method will panic. Only attribute access operations can be matched and replaced. Index steps never match the prefix.

Example
package main

import (
	"fmt"

	"github.com/hashicorp/hcl2/hcl"
	"github.com/hashicorp/hcl2/hclwrite"
)

func main() {
	src := []byte(
		"foo = a.x + a.y * b.c\n" +
			"bar = max(a.z, b.c)\n",
	)
	f, diags := hclwrite.ParseConfig(src, "", hcl.Pos{Line: 1, Column: 1})
	if diags.HasErrors() {
		fmt.Printf("errors: %s", diags)
		return
	}

	// Rename references of variable "a" to "z"
	for _, attr := range f.Body().Attributes() {
		attr.Expr().RenameVariablePrefix(
			[]string{"a"},
			[]string{"z"},
		)
	}

	fmt.Printf("%s", f.Bytes())
}
Output:

foo = z.x + z.y * b.c
bar = max(z.z, b.c)

func (*Expression) Variables

func (e *Expression) Variables() []*Traversal

Variables returns the absolute traversals that exist within the receiving expression.

type File

type File struct {
	// contains filtered or unexported fields
}

func NewEmptyFile

func NewEmptyFile() *File

NewEmptyFile constructs a new file with no content, ready to be mutated by other calls that append to its body.

func NewFile

func NewFile() *File

NewFile creates a new file object that is empty and ready to have constructs added t it.

func ParseConfig

func ParseConfig(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics)

ParseConfig interprets the given source bytes into a *hclwrite.File. The resulting AST can be used to perform surgical edits on the source code before turning it back into bytes again.

func (*File) Body

func (f *File) Body() *Body

Body returns the root body of the file, which contains the top-level attributes and blocks.

func (*File) BuildTokens

func (it *File) BuildTokens(to Tokens) Tokens

func (*File) Bytes

func (f *File) Bytes() []byte

Bytes returns a buffer containing the source code resulting from the tokens underlying the receiving file. If any updates have been made via the AST API, these will be reflected in the result.

func (*File) WriteTo

func (f *File) WriteTo(wr io.Writer) (int64, error)

WriteTo writes the tokens underlying the receiving file to the given writer.

The tokens first have a simple formatting pass applied that adjusts only the spaces between them.

type Token

type Token struct {
	Type  hclsyntax.TokenType
	Bytes []byte

	// We record the number of spaces before each token so that we can
	// reproduce the exact layout of the original file when we're making
	// surgical changes in-place. When _new_ code is created it will always
	// be in the canonical style, but we preserve layout of existing code.
	SpacesBefore int
}

Token is a single sequence of bytes annotated with a type. It is similar in purpose to hclsyntax.Token, but discards the source position information since that is not useful in code generation.

type Tokens

type Tokens []*Token

Tokens is a flat list of tokens.

func TokensForTraversal

func TokensForTraversal(traversal hcl.Traversal) Tokens

TokensForTraversal returns a sequence of tokens that represents the given traversal.

If the traversal is absolute then the result is a self-contained, valid reference expression. If the traversal is relative then the returned tokens could be appended to some other expression tokens to traverse into the represented expression.

func TokensForValue

func TokensForValue(val cty.Value) Tokens

TokensForValue returns a sequence of tokens that represents the given constant value.

This function only supports types that are used by HCL. In particular, it does not support capsule types and will panic if given one.

It is not possible to express an unknown value in source code, so this function will panic if the given value is unknown or contains any unknown values. A caller can call the value's IsWhollyKnown method to verify that no unknown values are present before calling TokensForValue.

func (Tokens) BuildTokens

func (ts Tokens) BuildTokens(to Tokens) Tokens

func (Tokens) Bytes

func (ts Tokens) Bytes() []byte

func (Tokens) Columns

func (ts Tokens) Columns() int

Columns returns the number of columns (grapheme clusters) the token sequence occupies. The result is not meaningful if there are newline or single-line comment tokens in the sequence.

func (Tokens) WriteTo

func (ts Tokens) WriteTo(wr io.Writer) (int64, error)

WriteTo takes an io.Writer and writes the bytes for each token to it, along with the spacing that separates each token. In other words, this allows serializing the tokens to a file or other such byte stream.

type Traversal

type Traversal struct {
	// contains filtered or unexported fields
}

Traversal represents a sequence of variable, attribute, and/or index operations.

func (*Traversal) BuildTokens

func (it *Traversal) BuildTokens(to Tokens) Tokens

type TraverseIndex

type TraverseIndex struct {
	// contains filtered or unexported fields
}

func (*TraverseIndex) BuildTokens

func (it *TraverseIndex) BuildTokens(to Tokens) Tokens

type TraverseName

type TraverseName struct {
	// contains filtered or unexported fields
}

func (*TraverseName) BuildTokens

func (it *TraverseName) BuildTokens(to Tokens) Tokens

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL