gohcl

package
v0.0.0-...-c924774 Latest Latest
Warning

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

Go to latest
Published: May 29, 2024 License: MPL-2.0 Imports: 9 Imported by: 0

README

This is a temporary fork of the github.com/hashicorp/hcl/gohcl/v2 package. It is in the process of being upstreamed

Documentation

Overview

Package gohcl allows decoding HCL configurations into Go data structures.

It provides a convenient and concise way of describing the schema for configuration and then accessing the resulting data via native Go types.

A struct field tag scheme is used, similar to other decoding and unmarshalling libraries. The tags are formatted as in the following example:

ThingType string `hcl:"thing_type,attr"`

Within each tag there are two comma-separated tokens. The first is the name of the corresponding construct in configuration, while the second is a keyword giving the kind of construct expected. The following kind keywords are supported:

attr (the default) indicates that the value is to be populated from an attribute
block indicates that the value is to populated from a block
label indicates that the value is to populated from a block label
optional is the same as attr, but the field is optional
remain indicates that the value is to be populated from the remaining body after populating other fields

"attr" fields may either be of type *hcl.Expression, in which case the raw expression is assigned, or of any type accepted by gocty, in which case gocty will be used to assign the value to a native Go type.

"block" fields may be a struct that recursively uses the same tags, or a slice of such structs, in which case multiple blocks of the corresponding type are decoded into the slice.

"body" can be placed on a single field of type hcl.Body to capture the full hcl.Body that was decoded for a block. This does not allow leftover values like "remain", so a decoding error will still be returned if leftover fields are given. If you want to capture the decoding body PLUS leftover fields, you must specify a "remain" field as well to prevent errors. The body field and the remain field will both contain the leftover fields.

"label" fields are considered only in a struct used as the type of a field marked as "block", and are used sequentially to capture the labels of the blocks being decoded. In this case, the name token is used only as an identifier for the label in diagnostic messages.

"optional" fields behave like "attr" fields, but they are optional and will not give parsing errors if they are missing.

"remain" can be placed on a single field that may be either of type hcl.Body or hcl.Attributes, in which case any remaining body content is placed into this field for delayed processing. If no "remain" field is present then any attributes or blocks not matched by another valid tag will cause an error diagnostic.

Only a subset of this tagging/typing vocabulary is supported for the "Encode" family of functions. See the EncodeIntoBody docs for full details on the constraints there.

Broadly-speaking this package deals with two types of error. The first is errors in the configuration itself, which are returned as diagnostics written with the configuration author as the target audience. The second is bugs in the calling program, such as invalid struct tags, which are surfaced via panics since there can be no useful runtime handling of such errors and they should certainly not be returned to the user as diagnostics.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeBody

func DecodeBody(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics

DecodeBody extracts the configuration within the given body into the given value. This value must be a non-nil pointer to either a struct or a map, where in the former case the configuration will be decoded using struct tags and in the latter case only attributes are allowed and their values are decoded into the map.

The given EvalContext is used to resolve any variables or functions in expressions encountered while decoding. This may be nil to require only constant values, for simple applications that do not support variables or functions.

The returned diagnostics should be inspected with its HasErrors method to determine if the populated value is valid and complete. If error diagnostics are returned then the given value may have been partially-populated but may still be accessed by a careful caller for static analysis and editor integration use-cases.

func DecodeExpression

func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics

DecodeExpression extracts the value of the given expression into the given value. This value must be something that gocty is able to decode into, since the final decoding is delegated to that package. If a reference to a struct is provided which contains gohcl tags, it will be decoded using the attr and optional tags.

The given EvalContext is used to resolve any variables or functions in expressions encountered while decoding. This may be nil to require only constant values, for simple applications that do not support variables or functions.

The returned diagnostics should be inspected with its HasErrors method to determine if the populated value is valid and complete. If error diagnostics are returned then the given value may have been partially-populated but may still be accessed by a careful caller for static analysis and editor integration use-cases.

func DecodeValue

func DecodeValue(srcVal cty.Value, subject hcl.Range, context hcl.Range, val interface{}) hcl.Diagnostics

DecodeValue extracts the given value into the provided target. This value must be something that gocty is able to decode into, since the final decoding is delegated to that package. If a reference to a struct is provided which contains gohcl tags, it will be decoded using the attr and optional tags.

The returned diagnostics should be inspected with its HasErrors method to determine if the populated value is valid and complete. If error diagnostics are returned then the given value may have been partially-populated but may still be accessed by a careful caller for static analysis and editor integration use-cases.

func EncodeAsBlock

func EncodeAsBlock(val interface{}, blockType string) *hclwrite.Block

EncodeAsBlock creates a new hclwrite.Block populated with the data from the given value, which must be a struct or pointer to struct with the struct tags defined in this package.

If the given struct type has fields tagged with "label" tags then they will be used in order to annotate the created block with labels.

This function has the same constraints as EncodeIntoBody and will panic if they are violated.

func EncodeIntoBody

func EncodeIntoBody(val interface{}, dst *hclwrite.Body)

EncodeIntoBody replaces the contents of the given hclwrite Body with attributes and blocks derived from the given value, which must be a struct value or a pointer to a struct value with the struct tags defined in this package.

This function can work only with fully-decoded data. It will ignore any fields tagged as "remain", any fields that decode attributes into either hcl.Attribute or hcl.Expression values, and any fields that decode blocks into hcl.Attributes values. This function does not have enough information to complete the decoding of these types.

Any fields tagged as "label" are ignored by this function. Use EncodeAsBlock to produce a whole hclwrite.Block including block labels.

As long as a suitable value is given to encode and the destination body is non-nil, this function will always complete. It will panic in case of any errors in the calling program, such as passing an inappropriate type or a nil body.

The layout of the resulting HCL source is derived from the ordering of the struct fields, with blank lines around nested blocks of different types. Fields representing attributes should usually precede those representing blocks so that the attributes can group togather in the result. For more control, use the hclwrite API directly.

Example
package main

import (
	"fmt"

	"github.com/hashicorp/hcl/v2/gohcl"
	"github.com/hashicorp/hcl/v2/hclwrite"
)

func main() {
	type Service struct {
		Name string   `hcl:"name,label"`
		Exe  []string `hcl:"executable"`
	}
	type Constraints struct {
		OS   string `hcl:"os"`
		Arch string `hcl:"arch"`
	}
	type App struct {
		Name        string       `hcl:"name"`
		Desc        string       `hcl:"description"`
		Constraints *Constraints `hcl:"constraints,block"`
		Services    []Service    `hcl:"service,block"`
	}

	app := App{
		Name: "awesome-app",
		Desc: "Such an awesome application",
		Constraints: &Constraints{
			OS:   "linux",
			Arch: "amd64",
		},
		Services: []Service{
			{
				Name: "web",
				Exe:  []string{"./web", "--listen=:8080"},
			},
			{
				Name: "worker",
				Exe:  []string{"./worker"},
			},
		},
	}

	f := hclwrite.NewEmptyFile()
	gohcl.EncodeIntoBody(&app, f.Body())
	fmt.Printf("%s", f.Bytes())

}
Output:

name        = "awesome-app"
description = "Such an awesome application"

constraints {
  os   = "linux"
  arch = "amd64"
}

service "web" {
  executable = ["./web", "--listen=:8080"]
}
service "worker" {
  executable = ["./worker"]
}

func ImpliedBodySchema

func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool)

ImpliedBodySchema produces a hcl.BodySchema derived from the type of the given value, which must be a struct value or a pointer to one. If an inappropriate value is passed, this function will panic.

The second return argument indicates whether the given struct includes a "remain" field, and thus the returned schema is non-exhaustive.

This uses the tags on the fields of the struct to discover how each field's value should be expressed within configuration. If an invalid mapping is attempted, this function will panic.

func VariablesInBody

func VariablesInBody(body hcl.Body, val interface{}) ([]hcl.Traversal, hcl.Diagnostics)

Types

This section is empty.

Jump to

Keyboard shortcuts

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