gohcl

package module
v0.0.0-...-5331269 Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2022 License: MPL-2.0 Imports: 14 Imported by: 6

README

gohcl

NOTE: gohcl is a work in progress and may have many bugs, user beware.

gohcl is a fork of github.com/hashicorp/hcl/v2/gohcl and github.com/zclconf/go-cty/cty/gocty which includes improvements over the existing package at the expense of making backwards-incompatible changes to the API.

It makes the following changes:

  • Capsule types can be used through a registration system.
  • nil slices will return an empty go-cty list or go-cty set instead of null.
  • It is valid for capsule values to be nil.
  • hcl struct tags are used globally instead of a combination of hcl and cty tags.
  • Encoding a value into a HCL body will no longer clear the existing contents of the body.
  • Capsule types can be encoded as HCL attributes through the use of capsule extension operations; allowing a capsule type to return raw HCL tokens which represent the capsule value.
  • Special handling for time.Duration and binary.TextMarshaler/binary.TextUnmarshaler
  • Support for an interface which exposes a DecodeHCL method

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 that can be converted into a native Go type.

"block" fields may be of type *hcl.Block or hcl.Body, in which case the corresponding raw value is assigned, or may be a struct that recursively uses the same tags. Block fields may also be slices of any of these types, 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.

Values in Go structs must be convertible to and from cty values. Values which are unsupported by default can be registered as a cty capsule type by calling RegisterCapsuleType.

Index

Examples

Constants

This section is empty.

Variables

View Source
var CapsuleTokenExtensionKey tokensExtension

CapsuleTokenExtensionKey is the key used for capsule types which can be encoded to HCL. If a capsule type implements this extension with a value of CapsuleTokenExtension, the result of the function call will be used to set tokens for an attribute.

If a capsule type does *not* implement this extension, encoding will panic.

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.

To decode a Block into a value implementing the Decoder interface, DecodeBody calls that value's DecodeHCL method.

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 can decode into a cty value.

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 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/hclwrite"
	"github.com/rfratto/gohcl"
)

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 FromCtyValue

func FromCtyValue(val cty.Value, target interface{}) error

FromCtyValue assigns a cty.Value to a reflect.Value, which must be a pointer, using a fixed set of conversion rules.

This function considers its audience to be the creator of the cty Value given, and thus the error messages it generates are (unlike with ToCtyValue) presented in cty terminology that is generally appropriate to return to end-users in applications where cty data structures are built from user-provided configuration. In particular this means that if incorrect target types are provided by the calling application the resulting error messages are likely to be confusing, since we assume that the given target type is correct and the cty.Value is where the error lies.

If an error is returned, the target data structure may have been partially populated, but the degree to which this is true is an implementation detail that the calling application should not rely on.

The function will panic if given a non-pointer as the Go value target, since that is considered to be a bug in the calling program.

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 ImpliedType

func ImpliedType(gv interface{}) (cty.Type, error)

ImpliedType takes an arbitrary Go value (as an interface{}) and attempts to find a suitable cty.Type instance that could be used for a conversion with ToCtyValue. ImpliedType will use capsule types in its returned type if that was registered via RegisterCapsuleType.

Not all Go types can be represented as cty types, so an error may be returned which is usually considered to be a bug in the calling program.

func RegisterCapsuleType

func RegisterCapsuleType(ty cty.Type)

RegisterCapsuleType registers a capsule type for use with encoding/decoding cty and HCL values. RegisterCapsuleType must not be called with different capsule types that have the same underlying encapsulated type.

RegisterCapsuleType panics if ty is not a capsule type.

func ToCtyValue

func ToCtyValue(val interface{}, ty cty.Type) (cty.Value, error)

ToCtyValue produces a cty.Value from a Go value. The result will conform to the given type, or an error will be returned if this is not possible.

The target type serves as a hint to resolve ambiguities in the mapping. For example, the Go type set.Set tells us that the value is a set but does not describe the set's element type. This also allows for convenient conversions, such as populating a set from a slice rather than having to first explicitly instantiate a set.Set.

The audience of this function is assumed to be the developers of Go code that is integrating with cty, and thus the error messages it returns are presented from Go's perspective. These messages are thus not appropriate for display to end-users. An error returned from ToCtyValue represents a bug in the calling program, not user error.

Types

type CapsuleTokenExtension

type CapsuleTokenExtension func(cty.Value) hclwrite.Tokens

CapsuleTokenExtension is a function used by attributes of capsule types which can be encoded as HCL.

type Decoder

type Decoder interface {
	DecodeHCL(body hcl.Body, ctx *hcl.EvalContext) error
}

Decoder is the interface implemented by types that can deocde themselves from an *hcl.Block.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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