hcl

package module
v2.0.0-...-773df4a Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2023 License: MPL-2.0 Imports: 13 Imported by: 0

README

HCL

HCL is a toolkit for creating structured configuration languages that are both human- and machine-friendly, for use with command-line tools. Although intended to be generally useful, it is primarily targeted towards devops tools, servers, etc.

NOTE: This is major version 2 of HCL, whose Go API is incompatible with major version 1. Both versions are available for selection in Go Modules projects. HCL 2 cannot be imported from Go projects that are not using Go Modules. For more information, see our version selection guide.

HCL has both a native syntax, intended to be pleasant to read and write for humans, and a JSON-based variant that is easier for machines to generate and parse.

The HCL native syntax is inspired by libucl, nginx configuration, and others.

It includes an expression syntax that allows basic inline computation and, with support from the calling application, use of variables and functions for more dynamic configuration languages.

HCL provides a set of constructs that can be used by a calling application to construct a configuration language. The application defines which attribute names and nested block types are expected, and HCL parses the configuration file, verifies that it conforms to the expected structure, and returns high-level objects that the application can use for further processing.

package main

import (
	"log"

	"github.com/hashicorp/hcl/v2/hclsimple"
)

type Config struct {
	IOMode  string        `hcl:"io_mode"`
	Service ServiceConfig `hcl:"service,block"`
}

type ServiceConfig struct {
	Protocol   string          `hcl:"protocol,label"`
	Type       string          `hcl:"type,label"`
	ListenAddr string          `hcl:"listen_addr"`
	Processes  []ProcessConfig `hcl:"process,block"`
}

type ProcessConfig struct {
	Type    string   `hcl:"type,label"`
	Command []string `hcl:"command"`
}

func main() {
	var config Config
	err := hclsimple.DecodeFile("config.hcl", nil, &config)
	if err != nil {
		log.Fatalf("Failed to load configuration: %s", err)
	}
	log.Printf("Configuration is %#v", config)
}

A lower-level API is available for applications that need more control over the parsing, decoding, and evaluation of configuration. For more information, see the package documentation.

Why?

Newcomers to HCL often ask: why not JSON, YAML, etc?

Whereas JSON and YAML are formats for serializing data structures, HCL is a syntax and API specifically designed for building structured configuration formats.

HCL attempts to strike a compromise between generic serialization formats such as JSON and configuration formats built around full programming languages such as Ruby. HCL syntax is designed to be easily read and written by humans, and allows declarative logic to permit its use in more complex applications.

HCL is intended as a base syntax for configuration formats built around key-value pairs and hierarchical blocks whose structure is well-defined by the calling application, and this definition of the configuration structure allows for better error messages and more convenient definition within the calling application.

It can't be denied that JSON is very convenient as a lingua franca for interoperability between different pieces of software. Because of this, HCL defines a common configuration model that can be parsed from either its native syntax or from a well-defined equivalent JSON structure. This allows configuration to be provided as a mixture of human-authored configuration files in the native syntax and machine-generated files in JSON.

Information Model and Syntax

HCL is built around two primary concepts: attributes and blocks. In native syntax, a configuration file for a hypothetical application might look something like this:

io_mode = "async"

service "http" "web_proxy" {
  listen_addr = "127.0.0.1:8080"
  
  process "main" {
    command = ["/usr/local/bin/awesome-app", "server"]
  }

  process "mgmt" {
    command = ["/usr/local/bin/awesome-app", "mgmt"]
  }
}

The JSON equivalent of this configuration is the following:

{
  "io_mode": "async",
  "service": {
    "http": {
      "web_proxy": {
        "listen_addr": "127.0.0.1:8080",
        "process": {
          "main": {
            "command": ["/usr/local/bin/awesome-app", "server"]
          },
          "mgmt": {
            "command": ["/usr/local/bin/awesome-app", "mgmt"]
          },
        }
      }
    }
  }
}

Regardless of which syntax is used, the API within the calling application is the same. It can either work directly with the low-level attributes and blocks, for more advanced use-cases, or it can use one of the decoder packages to declaratively extract into either Go structs or dynamic value structures.

Attribute values can be expressions as well as just literal values:

# Arithmetic with literals and application-provided variables
sum = 1 + addend

# String interpolation and templates
message = "Hello, ${name}!"

# Application-provided functions
shouty_message = upper(message)

Although JSON syntax doesn't permit direct use of expressions, the interpolation syntax allows use of arbitrary expressions within JSON strings:

{
  "sum": "${1 + addend}",
  "message": "Hello, ${name}!",
  "shouty_message": "${upper(message)}"
}

For more information, see the detailed specifications:

Changes in 2.0

Version 2.0 of HCL combines the features of HCL 1.0 with those of the interpolation language HIL to produce a single configuration language that supports arbitrary expressions.

This new version has a completely new parser and Go API, with no direct migration path. Although the syntax is similar, the implementation takes some very different approaches to improve on some "rough edges" that existed with the original implementation and to allow for more robust error handling.

It's possible to import both HCL 1 and HCL 2 into the same program using Go's semantic import versioning mechanism:

import (
    hcl1 "github.com/hashicorp/hcl"
    hcl2 "github.com/hashicorp/hcl/v2"
)

Acknowledgements

HCL was heavily inspired by libucl, by Vsevolod Stakhov.

HCL and HIL originate in HashiCorp Terraform, with the original parsers for each written by Mitchell Hashimoto.

The original HCL parser was ported to pure Go (from yacc) by Fatih Arslan. The structure-related portions of the new native syntax parser build on that work.

The original HIL parser was ported to pure Go (from yacc) by Martin Atkins. The expression-related portions of the new native syntax parser build on that work.

HCL 2, which merged the original HCL and HIL languages into this single new language, builds on design and prototyping work by Martin Atkins in zcl.

Documentation

Overview

Package hcl contains the main modelling types and general utility functions for HCL.

For a simple entry point into HCL, see the package in the subdirectory "hclsimple", which has an opinionated function Decode that can decode HCL configurations in either native HCL syntax or JSON syntax into a Go struct type:

package main

import (
	"log"
	"github.com/hashicorp/hcl/v2/hclsimple"
)

type Config struct {
	LogLevel string `hcl:"log_level"`
}

func main() {
	var config Config
	err := hclsimple.DecodeFile("config.hcl", nil, &config)
	if err != nil {
		log.Fatalf("Failed to load configuration: %s", err)
	}
	log.Printf("Configuration is %#v", config)
}

If your application needs more control over the evaluation of the configuration, you can use the functions in the subdirectories hclparse, gohcl, hcldec, etc. Splitting the handling of configuration into multiple phases allows for advanced patterns such as allowing expressions in one part of the configuration to refer to data defined in another part.

Index

Constants

This section is empty.

Variables

View Source
var InitialPos = Pos{Byte: 0, Line: 1, Column: 1}

InitialPos is a suitable position to use to mark the start of a file.

Functions

func AbsTraversalForExpr

func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics)

AbsTraversalForExpr attempts to interpret the given expression as an absolute traversal, or returns error diagnostic(s) if that is not possible for the given expression.

A particular Expression implementation can support this function by offering a method called AsTraversal that takes no arguments and returns either a valid absolute traversal or nil to indicate that no traversal is possible. Alternatively, an implementation can support UnwrapExpression to delegate handling of this function to a wrapped Expression object.

In most cases the calling application is interested in the value that results from an expression, but in rarer cases the application needs to see the name of the variable and subsequent attributes/indexes itself, for example to allow users to give references to the variables themselves rather than to their values. An implementer of this function should at least support attribute and index steps.

func DiagnosticExtra

func DiagnosticExtra[T any](diag *Diagnostic) (T, bool)

DiagnosticExtra attempts to retrieve an "extra value" of type T from the given diagnostic, if either the diag.Extra field directly contains a value of that type or the value implements DiagnosticExtraUnwrapper and directly or indirectly returns a value of that type.

Type T should typically be an interface type, so that code which generates diagnostics can potentially return different implementations of the same interface dynamically as needed.

If a value of type T is found, returns that value and true to indicate success. Otherwise, returns the zero value of T and false to indicate failure.

func ExprAsKeyword

func ExprAsKeyword(expr Expression) string

ExprAsKeyword attempts to interpret the given expression as a static keyword, returning the keyword string if possible, and the empty string if not.

A static keyword, for the sake of this function, is a single identifier. For example, the following attribute has an expression that would produce the keyword "foo":

example = foo

This function is a variant of AbsTraversalForExpr, which uses the same interface on the given expression. This helper constrains the result further by requiring only a single root identifier.

This function is intended to be used with the following idiom, to recognize situations where one of a fixed set of keywords is required and arbitrary expressions are not allowed:

switch hcl.ExprAsKeyword(expr) {
case "allow":
    // (take suitable action for keyword "allow")
case "deny":
    // (take suitable action for keyword "deny")
default:
    diags = append(diags, &hcl.Diagnostic{
        // ... "invalid keyword" diagnostic message ...
    })
}

The above approach will generate the same message for both the use of an unrecognized keyword and for not using a keyword at all, which is usually reasonable if the message specifies that the given value must be a keyword from that fixed list.

Note that in the native syntax the keywords "true", "false", and "null" are recognized as literal values during parsing and so these reserved words cannot not be accepted as keywords by this function.

Since interpreting an expression as a keyword bypasses usual expression evaluation, it should be used sparingly for situations where e.g. one of a fixed set of keywords is used in a structural way in a special attribute to affect the further processing of a block.

func ExprCall

func ExprCall(expr Expression) (*StaticCall, Diagnostics)

ExprCall tests if the given expression is a function call and, if so, extracts the function name and the expressions that represent the arguments. If the given expression is not statically a function call, error diagnostics are returned.

A particular Expression implementation can support this function by offering a method called ExprCall that takes no arguments and returns *StaticCall. This method should return nil if a static call cannot be extracted. Alternatively, an implementation can support UnwrapExpression to delegate handling of this function to a wrapped Expression object.

func ExprList

func ExprList(expr Expression) ([]Expression, Diagnostics)

ExprList tests if the given expression is a static list construct and, if so, extracts the expressions that represent the list elements. If the given expression is not a static list, error diagnostics are returned.

A particular Expression implementation can support this function by offering a method called ExprList that takes no arguments and returns []Expression. This method should return nil if a static list cannot be extracted. Alternatively, an implementation can support UnwrapExpression to delegate handling of this function to a wrapped Expression object.

func ExprMap

func ExprMap(expr Expression) ([]KeyValuePair, Diagnostics)

ExprMap tests if the given expression is a static map construct and, if so, extracts the expressions that represent the map elements. If the given expression is not a static map, error diagnostics are returned.

A particular Expression implementation can support this function by offering a method called ExprMap that takes no arguments and returns []KeyValuePair. This method should return nil if a static map cannot be extracted. Alternatively, an implementation can support UnwrapExpression to delegate handling of this function to a wrapped Expression object.

func RelTraversalForExpr

func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics)

RelTraversalForExpr is similar to AbsTraversalForExpr but it returns a relative traversal instead. Due to the nature of HCL expressions, the first element of the returned traversal is always a TraverseAttr, and then it will be followed by zero or more other expressions.

Any expression accepted by AbsTraversalForExpr is also accepted by RelTraversalForExpr.

Types

type Attribute

type Attribute struct {
	Name string
	Expr Expression

	Range     Range
	NameRange Range
}

Attribute represents an attribute from within a body.

type AttributeSchema

type AttributeSchema struct {
	Name     string
	Required bool
}

AttributeSchema represents the requirements for an attribute, and is used for matching attributes within bodies.

type Attributes

type Attributes map[string]*Attribute

Attributes is a set of attributes keyed by their names.

type Block

type Block struct {
	Type   string
	Labels []string
	Body   Body

	DefRange    Range   // Range that can be considered the "definition" for seeking in an editor
	TypeRange   Range   // Range for the block type declaration specifically.
	LabelRanges []Range // Ranges for the label values specifically.
}

Block represents a nested block within a Body.

type BlockHeaderSchema

type BlockHeaderSchema struct {
	Type       string
	LabelNames []string
}

BlockHeaderSchema represents the shape of a block header, and is used for matching blocks within bodies.

type Blocks

type Blocks []*Block

Blocks is a sequence of Block.

func (Blocks) ByType

func (els Blocks) ByType() map[string]Blocks

ByType transforms the receiving block sequence into a map from type name to block sequences of only that type.

func (Blocks) OfType

func (els Blocks) OfType(typeName string) Blocks

OfType filters the receiving block sequence by block type name, returning a new block sequence including only the blocks of the requested type.

type Body

type Body interface {
	// Content verifies that the entire body content conforms to the given
	// schema and then returns it, and/or returns diagnostics. The returned
	// body content is valid if non-nil, regardless of whether Diagnostics
	// are provided, but diagnostics should still be eventually shown to
	// the user.
	Content(schema *BodySchema) (*BodyContent, Diagnostics)

	// PartialContent is like Content except that it permits the configuration
	// to contain additional blocks or attributes not specified in the
	// schema. If any are present, the returned Body is non-nil and contains
	// the remaining items from the body that were not selected by the schema.
	PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics)

	// JustAttributes attempts to interpret all of the contents of the body
	// as attributes, allowing for the contents to be accessed without a priori
	// knowledge of the structure.
	//
	// The behavior of this method depends on the body's source language.
	// Some languages, like JSON, can't distinguish between attributes and
	// blocks without schema hints, but for languages that _can_ error
	// diagnostics will be generated if any blocks are present in the body.
	//
	// Diagnostics may be produced for other reasons too, such as duplicate
	// declarations of the same attribute.
	JustAttributes() (Attributes, Diagnostics)

	// MissingItemRange returns a range that represents where a missing item
	// might hypothetically be inserted. This is used when producing
	// diagnostics about missing required attributes or blocks. Not all bodies
	// will have an obvious single insertion point, so the result here may
	// be rather arbitrary.
	MissingItemRange() Range
}

Body is a container for attributes and blocks. It serves as the primary unit of hierarchical structure within configuration.

The content of a body cannot be meaningfully interpreted without a schema, so Body represents the raw body content and has methods that allow the content to be extracted in terms of a given schema.

func EmptyBody

func EmptyBody() Body

EmptyBody returns a body with no content. This body can be used as a placeholder when a body is required but no body content is available.

func MergeBodies

func MergeBodies(bodies []Body) Body

MergeBodies is like MergeFiles except it deals directly with bodies, rather than with entire files.

func MergeFiles

func MergeFiles(files []*File) Body

MergeFiles combines the given files to produce a single body that contains configuration from all of the given files.

The ordering of the given files decides the order in which contained elements will be returned. If any top-level attributes are defined with the same name across multiple files, a diagnostic will be produced from the Content and PartialContent methods describing this error in a user-friendly way.

type BodyContent

type BodyContent struct {
	Attributes Attributes
	Blocks     Blocks

	MissingItemRange Range
}

BodyContent is the result of applying a BodySchema to a Body.

type BodySchema

type BodySchema struct {
	Attributes []AttributeSchema
	Blocks     []BlockHeaderSchema
}

BodySchema represents the desired shallow structure of a body.

type Diagnostic

type Diagnostic struct {
	Severity DiagnosticSeverity

	// Summary and Detail contain the English-language description of the
	// problem. Summary is a terse description of the general problem and
	// detail is a more elaborate, often-multi-sentence description of
	// the problem and what might be done to solve it.
	Summary string
	Detail  string

	// Subject and Context are both source ranges relating to the diagnostic.
	//
	// Subject is a tight range referring to exactly the construct that
	// is problematic, while Context is an optional broader range (which should
	// fully contain Subject) that ought to be shown around Subject when
	// generating isolated source-code snippets in diagnostic messages.
	// If Context is nil, the Subject is also the Context.
	//
	// Some diagnostics have no source ranges at all. If Context is set then
	// Subject should always also be set.
	Subject *Range
	Context *Range

	// For diagnostics that occur when evaluating an expression, Expression
	// may refer to that expression and EvalContext may point to the
	// EvalContext that was active when evaluating it. This may allow for the
	// inclusion of additional useful information when rendering a diagnostic
	// message to the user.
	//
	// It is not always possible to select a single EvalContext for a
	// diagnostic, and so in some cases this field may be nil even when an
	// expression causes a problem.
	//
	// EvalContexts form a tree, so the given EvalContext may refer to a parent
	// which in turn refers to another parent, etc. For a full picture of all
	// of the active variables and functions the caller must walk up this
	// chain, preferring definitions that are "closer" to the expression in
	// case of colliding names.
	Expression  Expression
	EvalContext *EvalContext

	// Extra is an extension point for additional machine-readable information
	// about this problem.
	//
	// Recipients of diagnostic objects may type-assert this value with
	// specific interface types they know about to discover if any additional
	// information is available that is interesting for their use-case.
	//
	// Extra is always considered to be optional extra information and so a
	// diagnostic message should still always be fully described (from the
	// perspective of a human who understands the language the messages are
	// written in) by the other fields in case a particular recipient.
	//
	// Functions that return diagnostics with Extra populated should typically
	// document that they place values implementing a particular interface,
	// rather than a concrete type, and define that interface such that its
	// methods can dynamically indicate a lack of support at runtime even
	// if the interface happens to be statically available. An Extra
	// type that wraps other Extra values should additionally implement
	// interface DiagnosticExtraUnwrapper to return the value they are wrapping
	// so that callers can access inner values to type-assert against.
	Extra interface{}
}

Diagnostic represents information to be presented to a user about an error or anomaly in parsing or evaluating configuration.

func (*Diagnostic) Error

func (d *Diagnostic) Error() string

error implementation, so that diagnostics can be returned via APIs that normally deal in vanilla Go errors.

This presents only minimal context about the error, for compatibility with usual expectations about how errors will present as strings.

type DiagnosticExtraUnwrapper

type DiagnosticExtraUnwrapper interface {
	// If the reciever is wrapping another "diagnostic extra" value, returns
	// that value. Otherwise returns nil to indicate dynamically that nothing
	// is wrapped.
	//
	// The "nothing is wrapped" condition can be signalled either by this
	// method returning nil or by a type not implementing this interface at all.
	//
	// Implementers should never create unwrap "cycles" where a nested extra
	// value returns a value that was also wrapping it.
	UnwrapDiagnosticExtra() interface{}
}

DiagnosticExtraUnwrapper is an interface implemented by values in the Extra field of Diagnostic when they are wrapping another "Extra" value that was generated downstream.

Diagnostic recipients which want to examine "Extra" values to sniff for particular types of extra data can either type-assert this interface directly and repeatedly unwrap until they recieve nil, or can use the helper function DiagnosticExtra.

type DiagnosticSeverity

type DiagnosticSeverity int

DiagnosticSeverity represents the severity of a diagnostic.

const (
	// DiagInvalid is the invalid zero value of DiagnosticSeverity
	DiagInvalid DiagnosticSeverity = iota

	// DiagError indicates that the problem reported by a diagnostic prevents
	// further progress in parsing and/or evaluating the subject.
	DiagError

	// DiagWarning indicates that the problem reported by a diagnostic warrants
	// user attention but does not prevent further progress. It is most
	// commonly used for showing deprecation notices.
	DiagWarning
)

type DiagnosticWriter

type DiagnosticWriter interface {
	WriteDiagnostic(*Diagnostic) error
	WriteDiagnostics(Diagnostics) error
}

A DiagnosticWriter emits diagnostics somehow.

func NewDiagnosticTextWriter

func NewDiagnosticTextWriter(wr io.Writer, files map[string]*File, width uint, color bool) DiagnosticWriter

NewDiagnosticTextWriter creates a DiagnosticWriter that writes diagnostics to the given writer as formatted text.

It is designed to produce text appropriate to print in a monospaced font in a terminal of a particular width, or optionally with no width limit.

The given width may be zero to disable word-wrapping of the detail text and truncation of source code snippets.

If color is set to true, the output will include VT100 escape sequences to color-code the severity indicators. It is suggested to turn this off if the target writer is not a terminal.

type Diagnostics

type Diagnostics []*Diagnostic

Diagnostics is a list of Diagnostic instances.

func ApplyPath

func ApplyPath(val cty.Value, path cty.Path, srcRange *Range) (cty.Value, Diagnostics)

ApplyPath is a helper function that applies a cty.Path to a value using the indexing and attribute access operations from HCL.

This is similar to calling the path's own Apply method, but ApplyPath uses the more relaxed typing rules that apply to these operations in HCL, rather than cty's relatively-strict rules. ApplyPath is implemented in terms of Index and GetAttr, and so it has the same behavior for individual steps but will stop and return any errors returned by intermediate steps.

Diagnostics are produced if the given path cannot be applied to the given value. Therefore a pointer to a source range must be provided to use in diagnostics, though nil can be provided if the calling application is going to ignore the subject of the returned diagnostics anyway.

func GetAttr

func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagnostics)

GetAttr is a helper function that performs the same operation as the attribute access in the HCL expression language. That is, the result is the same as it would be for obj.attr in a configuration expression.

This is exported so that applications can access attributes in a manner consistent with how the language does it, including handling of null and unknown values, etc.

Diagnostics are produced if the given combination of values is not valid. Therefore a pointer to a source range must be provided to use in diagnostics, though nil can be provided if the calling application is going to ignore the subject of the returned diagnostics anyway.

func Index

func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)

Index is a helper function that performs the same operation as the index operator in the HCL expression language. That is, the result is the same as it would be for collection[key] in a configuration expression.

This is exported so that applications can perform indexing in a manner consistent with how the language does it, including handling of null and unknown values, etc.

Diagnostics are produced if the given combination of values is not valid. Therefore a pointer to a source range must be provided to use in diagnostics, though nil can be provided if the calling application is going to ignore the subject of the returned diagnostics anyway.

func (Diagnostics) Append

func (d Diagnostics) Append(diag *Diagnostic) Diagnostics

Append appends a new error to a Diagnostics and return the whole Diagnostics.

This is provided as a convenience for returning from a function that collects and then returns a set of diagnostics:

return nil, diags.Append(&hcl.Diagnostic{ ... })

Note that this modifies the array underlying the diagnostics slice, so must be used carefully within a single codepath. It is incorrect (and rude) to extend a diagnostics created by a different subsystem.

func (Diagnostics) Error

func (d Diagnostics) Error() string

error implementation, so that sets of diagnostics can be returned via APIs that normally deal in vanilla Go errors.

func (Diagnostics) Errs

func (d Diagnostics) Errs() []error

func (Diagnostics) Extend

func (d Diagnostics) Extend(diags Diagnostics) Diagnostics

Extend concatenates the given Diagnostics with the receiver and returns the whole new Diagnostics.

This is similar to Append but accepts multiple diagnostics to add. It has all the same caveats and constraints.

func (Diagnostics) HasErrors

func (d Diagnostics) HasErrors() bool

HasErrors returns true if the receiver contains any diagnostics of severity DiagError.

type EvalContext

type EvalContext struct {
	Variables map[string]cty.Value
	Functions map[string]function.Function
	// contains filtered or unexported fields
}

An EvalContext provides the variables and functions that should be used to evaluate an expression.

func (*EvalContext) NewChild

func (ctx *EvalContext) NewChild() *EvalContext

NewChild returns a new EvalContext that is a child of the receiver.

func (*EvalContext) Parent

func (ctx *EvalContext) Parent() *EvalContext

Parent returns the parent of the receiver, or nil if the receiver has no parent.

type Expression

type Expression interface {
	// Value returns the value resulting from evaluating the expression
	// in the given evaluation context.
	//
	// The context may be nil, in which case the expression may contain
	// only constants and diagnostics will be produced for any non-constant
	// sub-expressions. (The exact definition of this depends on the source
	// language.)
	//
	// The context may instead be set but have either its Variables or
	// Functions maps set to nil, in which case only use of these features
	// will return diagnostics.
	//
	// Different diagnostics are provided depending on whether the given
	// context maps are nil or empty. In the former case, the message
	// tells the user that variables/functions are not permitted at all,
	// while in the latter case usage will produce a "not found" error for
	// the specific symbol in question.
	Value(ctx *EvalContext) (cty.Value, Diagnostics)

	// Variables returns a list of variables referenced in the receiving
	// expression. These are expressed as absolute Traversals, so may include
	// additional information about how the variable is used, such as
	// attribute lookups, which the calling application can potentially use
	// to only selectively populate the scope.
	Variables() []Traversal

	Range() Range
	StartRange() Range
}

Expression is a literal value or an expression provided in the configuration, which can be evaluated within a scope to produce a value.

func StaticExpr

func StaticExpr(val cty.Value, rng Range) Expression

StaticExpr returns an Expression that always evaluates to the given value.

This is useful to substitute default values for expressions that are not explicitly given in configuration and thus would otherwise have no Expression to return.

Since expressions are expected to have a source range, the caller must provide one. Ideally this should be a real source range, but it can be a synthetic one (with an empty-string filename) if no suitable range is available.

func UnwrapExpression

func UnwrapExpression(expr Expression) Expression

UnwrapExpression removes any "wrapper" expressions from the given expression, to recover the representation of the physical expression given in source code.

Sometimes wrapping expressions are used to modify expression behavior, e.g. in extensions that need to make some local variables available to certain sub-trees of the configuration. This can make it difficult to reliably type-assert on the physical AST types used by the underlying syntax.

Unwrapping an expression may modify its behavior by stripping away any additional constraints or capabilities being applied to the Value and Variables methods, so this function should generally only be used prior to operations that concern themselves with the static syntax of the input configuration, and not with the effective value of the expression.

Wrapper expression types must support unwrapping by implementing a method called UnwrapExpression that takes no arguments and returns the embedded Expression. Implementations of this method should peel away only one level of wrapping, if multiple are present. This method may return nil to indicate _dynamically_ that no wrapped expression is available, for expression types that might only behave as wrappers in certain cases.

func UnwrapExpressionUntil

func UnwrapExpressionUntil(expr Expression, until func(Expression) bool) Expression

UnwrapExpressionUntil is similar to UnwrapExpression except it gives the caller an opportunity to test each level of unwrapping to see each a particular expression is accepted.

This could be used, for example, to unwrap until a particular other interface is satisfied, regardless of wrap wrapping level it is satisfied at.

The given callback function must return false to continue wrapping, or true to accept and return the proposed expression given. If the callback function rejects even the final, physical expression then the result of this function is nil.

type File

type File struct {
	Body  Body
	Bytes []byte

	// Nav is used to integrate with the "hcled" editor integration package,
	// and with diagnostic information formatters. It is not for direct use
	// by a calling application.
	Nav interface{}
}

File is the top-level node that results from parsing a HCL file.

func (*File) AttributeAtPos

func (f *File) AttributeAtPos(pos Pos) *Attribute

AttributeAtPos attempts to find an attribute definition in the receiving file that contains the given position. This is a best-effort method that may not be able to produce a result for all positions or for all HCL syntaxes.

The result is nil if no single attribute could be selected for any reason.

func (*File) BlocksAtPos

func (f *File) BlocksAtPos(pos Pos) []*Block

BlocksAtPos attempts to find all of the blocks that contain the given position, ordered so that the outermost block is first and the innermost block is last. This is a best-effort method that may not be able to produce a complete result for all positions or for all HCL syntaxes.

If the returned slice is non-empty, the first element is guaranteed to represent the same block as would be the result of OutermostBlockAtPos and the last element the result of InnermostBlockAtPos. However, the implementation may return two different objects describing the same block, so comparison by pointer identity is not possible.

The result is nil if no blocks at all contain the given position.

func (*File) InnermostBlockAtPos

func (f *File) InnermostBlockAtPos(pos Pos) *Block

InnermostBlockAtPos attempts to find the most deeply-nested block in the receiving file that contains the given position. This is a best-effort method that may not be able to produce a result for all positions or for all HCL syntaxes.

The result is nil if no single block could be selected for any reason.

func (*File) OutermostBlockAtPos

func (f *File) OutermostBlockAtPos(pos Pos) *Block

OutermostBlockAtPos attempts to find a top-level block in the receiving file that contains the given position. This is a best-effort method that may not be able to produce a result for all positions or for all HCL syntaxes.

The result is nil if no single block could be selected for any reason.

func (*File) OutermostExprAtPos

func (f *File) OutermostExprAtPos(pos Pos) Expression

OutermostExprAtPos attempts to find an expression in the receiving file that contains the given position. This is a best-effort method that may not be able to produce a result for all positions or for all HCL syntaxes.

Since expressions are often nested inside one another, this method returns the outermost "root" expression that is not contained by any other.

The result is nil if no single expression could be selected for any reason.

type KeyValuePair

type KeyValuePair struct {
	Key   Expression
	Value Expression
}

KeyValuePair represents a pair of expressions that serve as a single item within a map or object definition construct.

type Pos

type Pos struct {
	// Line is the source code line where this position points. Lines are
	// counted starting at 1 and incremented for each newline character
	// encountered.
	Line int

	// Column is the source code column where this position points, in
	// unicode characters, with counting starting at 1.
	//
	// Column counts characters as they appear visually, so for example a
	// latin letter with a combining diacritic mark counts as one character.
	// This is intended for rendering visual markers against source code in
	// contexts where these diacritics would be rendered in a single character
	// cell. Technically speaking, Column is counting grapheme clusters as
	// used in unicode normalization.
	Column int

	// Byte is the byte offset into the file where the indicated character
	// begins. This is a zero-based offset to the first byte of the first
	// UTF-8 codepoint sequence in the character, and thus gives a position
	// that can be resolved _without_ awareness of Unicode characters.
	Byte int
}

Pos represents a single position in a source file, by addressing the start byte of a unicode character encoded in UTF-8.

Pos is generally used only in the context of a Range, which then defines which source file the position is within.

type Range

type Range struct {
	// Filename is the name of the file into which this range's positions
	// point.
	Filename string

	// Start and End represent the bounds of this range. Start is inclusive
	// and End is exclusive.
	Start, End Pos
}

Range represents a span of characters between two positions in a source file.

This struct is usually used by value in types that represent AST nodes, but by pointer in types that refer to the positions of other objects, such as in diagnostics.

func RangeBetween

func RangeBetween(start, end Range) Range

RangeBetween returns a new range that spans from the beginning of the start range to the end of the end range.

The result is meaningless if the two ranges do not belong to the same source file or if the end range appears before the start range.

func RangeOver

func RangeOver(a, b Range) Range

RangeOver returns a new range that covers both of the given ranges and possibly additional content between them if the two ranges do not overlap.

If either range is empty then it is ignored. The result is empty if both given ranges are empty.

The result is meaningless if the two ranges to not belong to the same source file.

func (Range) CanSliceBytes

func (r Range) CanSliceBytes(b []byte) bool

CanSliceBytes returns true if SliceBytes could return an accurate sub-slice of the given slice.

This effectively tests whether the start and end offsets of the range are within the bounds of the slice, and thus whether SliceBytes can be trusted to produce an accurate start and end position within that slice.

func (Range) ContainsOffset

func (r Range) ContainsOffset(offset int) bool

ContainsOffset returns true if and only if the given byte offset is within the receiving Range.

func (Range) ContainsPos

func (r Range) ContainsPos(pos Pos) bool

ContainsPos returns true if and only if the given position is contained within the receiving range.

In the unlikely case that the line/column information disagree with the byte offset information in the given position or receiving range, the byte offsets are given priority.

func (Range) Empty

func (r Range) Empty() bool

func (Range) Overlap

func (r Range) Overlap(other Range) Range

Overlap finds a range that is either identical to or a sub-range of both the receiver and the other given range. It returns an empty range within the receiver if there is no overlap between the two ranges.

A non-empty result is either identical to or a subset of the receiver.

func (Range) Overlaps

func (r Range) Overlaps(other Range) bool

Overlaps returns true if the receiver and the other given range share any characters in common.

func (Range) PartitionAround

func (r Range) PartitionAround(other Range) (before, overlap, after Range)

PartitionAround finds the portion of the given range that overlaps with the reciever and returns three ranges: the portion of the reciever that precedes the overlap, the overlap itself, and then the portion of the reciever that comes after the overlap.

If the two ranges do not overlap then all three returned ranges are empty.

If the given range aligns with or extends beyond either extent of the reciever then the corresponding outer range will be empty.

func (Range) Ptr

func (r Range) Ptr() *Range

Ptr returns a pointer to a copy of the receiver. This is a convenience when ranges in places where pointers are required, such as in Diagnostic, but the range in question is returned from a method. Go would otherwise not allow one to take the address of a function call.

func (Range) SliceBytes

func (r Range) SliceBytes(b []byte) []byte

SliceBytes returns a sub-slice of the given slice that is covered by the receiving range, assuming that the given slice is the source code of the file indicated by r.Filename.

If the receiver refers to any byte offsets that are outside of the slice then the result is constrained to the overlapping portion only, to avoid a panic. Use CanSliceBytes to determine if the result is guaranteed to be an accurate span of the requested range.

func (Range) String

func (r Range) String() string

String returns a compact string representation of the receiver. Callers should generally prefer to present a range more visually, e.g. via markers directly on the relevant portion of source code.

type RangeScanner

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

RangeScanner is a helper that will scan over a buffer using a bufio.SplitFunc and visit a source range for each token matched.

For example, this can be used with bufio.ScanLines to find the source range for each line in the file, skipping over the actual newline characters, which may be useful when printing source code snippets as part of diagnostic messages.

The line and column information in the returned ranges is produced by counting newline characters and grapheme clusters respectively, which mimics the behavior we expect from a parser when producing ranges.

func NewRangeScanner

func NewRangeScanner(b []byte, filename string, cb bufio.SplitFunc) *RangeScanner

NewRangeScanner creates a new RangeScanner for the given buffer, producing ranges for the given filename.

Since ranges have grapheme-cluster granularity rather than byte granularity, the scanner will produce incorrect results if the given SplitFunc creates tokens between grapheme cluster boundaries. In particular, it is incorrect to use RangeScanner with bufio.ScanRunes because it will produce tokens around individual UTF-8 sequences, which will split any multi-sequence grapheme clusters.

func NewRangeScannerFragment

func NewRangeScannerFragment(b []byte, filename string, start Pos, cb bufio.SplitFunc) *RangeScanner

NewRangeScannerFragment is like NewRangeScanner but the ranges it produces will be offset by the given starting position, which is appropriate for sub-slices of a file, whereas NewRangeScanner assumes it is scanning an entire file.

func (*RangeScanner) Bytes

func (sc *RangeScanner) Bytes() []byte

Bytes returns the slice of the input buffer that is covered by the range that would be returned by Range.

func (*RangeScanner) Err

func (sc *RangeScanner) Err() error

Err can be called after Scan returns false to determine if the latest read resulted in an error, and obtain that error if so.

func (*RangeScanner) Range

func (sc *RangeScanner) Range() Range

Range returns a range that covers the latest token obtained after a call to Scan returns true.

func (*RangeScanner) Scan

func (sc *RangeScanner) Scan() bool

type StaticCall

type StaticCall struct {
	Name      string
	NameRange Range
	Arguments []Expression
	ArgsRange Range
}

StaticCall represents a function call that was extracted statically from an expression using ExprCall.

type Traversal

type Traversal []Traverser

A Traversal is a description of traversing through a value through a series of operations such as attribute lookup, index lookup, etc.

It is used to look up values in scopes, for example.

The traversal operations are implementations of interface Traverser. This is a closed set of implementations, so the interface cannot be implemented from outside this package.

A traversal can be absolute (its first value is a symbol name) or relative (starts from an existing value).

func TraversalJoin

func TraversalJoin(abs Traversal, rel Traversal) Traversal

TraversalJoin appends a relative traversal to an absolute traversal to produce a new absolute traversal.

func (Traversal) IsRelative

func (t Traversal) IsRelative() bool

IsRelative returns true if the receiver is a relative traversal, or false otherwise.

func (Traversal) RootName

func (t Traversal) RootName() string

RootName returns the root name for a absolute traversal. Will panic if called on a relative traversal.

func (Traversal) SimpleSplit

func (t Traversal) SimpleSplit() TraversalSplit

SimpleSplit returns a TraversalSplit where the name lookup is the absolute part and the remainder is the relative part. Supported only for absolute traversals, and will panic if applied to a relative traversal.

This can be used by applications that have a relatively-simple variable namespace where only the top-level is directly populated in the scope, with everything else handled by relative lookups from those initial values.

func (Traversal) SourceRange

func (t Traversal) SourceRange() Range

SourceRange returns the source range for the traversal.

func (Traversal) TraverseAbs

func (t Traversal) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics)

TraverseAbs applies the receiving traversal to the given eval context, returning the resulting value. This is supported only for absolute traversals, and will panic if applied to a relative traversal.

func (Traversal) TraverseRel

func (t Traversal) TraverseRel(val cty.Value) (cty.Value, Diagnostics)

TraverseRel applies the receiving traversal to the given value, returning the resulting value. This is supported only for relative traversals, and will panic if applied to an absolute traversal.

type TraversalSplit

type TraversalSplit struct {
	Abs Traversal
	Rel Traversal
}

TraversalSplit represents a pair of traversals, the first of which is an absolute traversal and the second of which is relative to the first.

This is used by calling applications that only populate prefixes of the traversals in the scope, with Abs representing the part coming from the scope and Rel representing the remaining steps once that part is retrieved.

func (TraversalSplit) Join

func (t TraversalSplit) Join() Traversal

Join concatenates together the Abs and Rel parts to produce a single absolute traversal.

func (TraversalSplit) RootName

func (t TraversalSplit) RootName() string

RootName returns the root name for the absolute part of the split.

func (TraversalSplit) Traverse

func (t TraversalSplit) Traverse(ctx *EvalContext) (cty.Value, Diagnostics)

Traverse is a convenience function to apply TraverseAbs followed by TraverseRel.

func (TraversalSplit) TraverseAbs

func (t TraversalSplit) TraverseAbs(ctx *EvalContext) (cty.Value, Diagnostics)

TraverseAbs traverses from a scope to the value resulting from the absolute traversal.

func (TraversalSplit) TraverseRel

func (t TraversalSplit) TraverseRel(val cty.Value) (cty.Value, Diagnostics)

TraverseRel traverses from a given value, assumed to be the result of TraverseAbs on some scope, to a final result for the entire split traversal.

type TraverseAttr

type TraverseAttr struct {
	Name     string
	SrcRange Range
	// contains filtered or unexported fields
}

TraverseAttr looks up an attribute in its initial value.

func (TraverseAttr) SourceRange

func (tn TraverseAttr) SourceRange() Range

func (TraverseAttr) TraversalStep

func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics)

type TraverseIndex

type TraverseIndex struct {
	Key      cty.Value
	SrcRange Range
	// contains filtered or unexported fields
}

TraverseIndex applies the index operation to its initial value.

func (TraverseIndex) SourceRange

func (tn TraverseIndex) SourceRange() Range

func (TraverseIndex) TraversalStep

func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics)

type TraverseRoot

type TraverseRoot struct {
	Name     string
	SrcRange Range
	// contains filtered or unexported fields
}

TraverseRoot looks up a root name in a scope. It is used as the first step of an absolute Traversal, and cannot itself be traversed directly.

func (TraverseRoot) SourceRange

func (tn TraverseRoot) SourceRange() Range

func (TraverseRoot) TraversalStep

func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics)

TraversalStep on a TraverseName immediately panics, because absolute traversals cannot be directly traversed.

type TraverseSplat

type TraverseSplat struct {
	Each     Traversal
	SrcRange Range
	// contains filtered or unexported fields
}

TraverseSplat applies the splat operation to its initial value.

func (TraverseSplat) SourceRange

func (tn TraverseSplat) SourceRange() Range

func (TraverseSplat) TraversalStep

func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics)

type Traverser

type Traverser interface {
	TraversalStep(cty.Value) (cty.Value, Diagnostics)
	SourceRange() Range
	// contains filtered or unexported methods
}

A Traverser is a step within a Traversal.

Directories

Path Synopsis
cmd
ext
customdecode
Package customdecode contains a HCL extension that allows, in certain contexts, expression evaluation to be overridden by custom static analysis.
Package customdecode contains a HCL extension that allows, in certain contexts, expression evaluation to be overridden by custom static analysis.
dynblock
Package dynblock provides an extension to HCL that allows dynamic declaration of nested blocks in certain contexts via a special block type named "dynamic".
Package dynblock provides an extension to HCL that allows dynamic declaration of nested blocks in certain contexts via a special block type named "dynamic".
transform
Package transform is a helper package for writing extensions that work by applying transforms to bodies.
Package transform is a helper package for writing extensions that work by applying transforms to bodies.
tryfunc
Package tryfunc contains some optional functions that can be exposed in HCL-based languages to allow authors to test whether a particular expression can succeed and take dynamic action based on that result.
Package tryfunc contains some optional functions that can be exposed in HCL-based languages to allow authors to test whether a particular expression can succeed and take dynamic action based on that result.
typeexpr
Package typeexpr extends HCL with a convention for describing HCL types within configuration files.
Package typeexpr extends HCL with a convention for describing HCL types within configuration files.
userfunc
Package userfunc implements a HCL extension that allows user-defined functions in HCL configuration.
Package userfunc implements a HCL extension that allows user-defined functions in HCL configuration.
Package gohcl allows decoding HCL configurations into Go data structures.
Package gohcl allows decoding HCL configurations into Go data structures.
Package hcldec provides a higher-level API for unpacking the content of HCL bodies, implemented in terms of the low-level "Content" API exposed by the bodies themselves.
Package hcldec provides a higher-level API for unpacking the content of HCL bodies, implemented in terms of the low-level "Content" API exposed by the bodies themselves.
Package hcled provides functionality intended to help an application that embeds HCL to deliver relevant information to a text editor or IDE for navigating around and analyzing configuration files.
Package hcled provides functionality intended to help an application that embeds HCL to deliver relevant information to a text editor or IDE for navigating around and analyzing configuration files.
Package hclparse has the main API entry point for parsing both HCL native syntax and HCL JSON.
Package hclparse has the main API entry point for parsing both HCL native syntax and HCL JSON.
Package hclsimple is a higher-level entry point for loading HCL configuration files directly into Go struct values in a single step.
Package hclsimple is a higher-level entry point for loading HCL configuration files directly into Go struct values in a single step.
Package hclsyntax contains the parser, AST, etc for HCL's native language, as opposed to the JSON variant.
Package hclsyntax contains the parser, AST, etc for HCL's native language, as opposed to the JSON variant.
Package hcltest contains utilities that aim to make it more convenient to write tests for code that interacts with the HCL API.
Package hcltest contains utilities that aim to make it more convenient to write tests for code that interacts with the HCL API.
Package hclwrite deals with the problem of generating HCL configuration and of making specific surgical changes to existing HCL configurations.
Package hclwrite deals with the problem of generating HCL configuration and of making specific surgical changes to existing HCL configurations.
Package integrationtest is an internal package that contains some tests that attempt to exercise many HCL features together in realistic scenarios.
Package integrationtest is an internal package that contains some tests that attempt to exercise many HCL features together in realistic scenarios.
Package json is the JSON parser for HCL.
Package json is the JSON parser for HCL.

Jump to

Keyboard shortcuts

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