Decoding Into Native Go Values¶
The most straightforward way to access the content of an HCL file is to
decode into native Go values using reflect
, similar to the technique used
by packages like encoding/json
and encoding/xml
.
Package gohcl
provides functions for this sort of decoding. Function
DecodeBody
attempts to extract values from an HCL body and write them
into a Go value given as a pointer:
type ServiceConfig struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
ListenAddr string `hcl:"listen_addr"`
}
type Config struct {
IOMode string `hcl:"io_mode"`
Services []ServiceConfig `hcl:"service,block"`
}
var c Config
moreDiags := gohcl.DecodeBody(f.Body, nil, &c)
diags = append(diags, moreDiags...)
The above example decodes the root body of a file f
, presumably loaded
previously using a parser, into the variable c
. The field labels within
the struct types imply the schema of the expected language, which is a cut-down
version of the hypothetical language we showed in Introduction to HCL.
The struct field labels consist of two comma-separated values. The first is
the name of the corresponding argument or block type as it will appear in
the input file, and the second is the type of element being named. If the
second value is omitted, it defaults to attr
, requesting an attribute.
Nested blocks are represented by a struct or a slice of that struct, and the
special element type label
within that struct declares that each instance
of that block type must be followed by one or more block labels. In the above
example, the service
block type is defined to require two labels, named
type
and name
. For label fields in particular, the given name is used
only to refer to the particular label in error messages when the wrong number
of labels is used.
By default, all declared attributes and blocks are considered to be required.
An optional value is indicated by making its field have a pointer type, in
which case nil
is written to indicate the absense of the argument.
The sections below discuss some additional decoding use-cases. For full details on the gohcl package, see the godoc reference.
Variables and Functions¶
By default, arguments given in the configuration may use only literal values and the built in expression language operators, such as arithmetic.
The second argument to gohcl.DecodeBody
, shown as nil
in the previous
example, allows the calling application to additionally offer variables and
functions for use in expressions. Its value is a pointer to an
hcl.EvalContext
, which will be covered in more detail in the later section
Expression Evaluation. For now, a simple example of making the id of the
current process available as a single variable called pid
:
type Context struct {
Pid string
}
ctx := gohcl.EvalContext(&Context{
Pid: os.Getpid()
})
var c Config
moreDiags := gohcl.DecodeBody(f.Body, ctx, &c)
diags = append(diags, moreDiags...)
gohcl.EvalContext
constructs an expression evaluation context from a Go
struct value, making the fields available as variables and the methods
available as functions, after transforming the field and method names such
that each word (starting with an uppercase letter) is all lowercase and
separated by underscores.
name = "example-program (${pid})"
Partial Decoding¶
In the examples so far, we’ve extracted the content from the entire input file
in a single call to DecodeBody
. This is sufficient for many simple
situations, but sometimes different parts of the file must be evaluated
separately. For example:
- If different parts of the file must be evaluated with different variables or functions available.
- If the result of evaluating one part of the file is used to set variables or functions in another part of the file.
There are several ways to perform partial decoding with gohcl
, all of
which involve decoding into HCL’s own types, such as hcl.Body
.
The most general approach is to declare an additional struct field of type
hcl.Body
, with the special field tag type remain
:
type ServiceConfig struct {
Type string `hcl:"type,label"`
Name string `hcl:"name,label"`
ListenAddr string `hcl:"listen_addr"`
Remain hcl.Body `hcl:",remain"`
}
When a remain
field is present, any element of the input body that is
not matched is retained in a body saved into that field, which can then be
decoded in a later call, potentially with a different evaluation context.
Another option is to decode an attribute into a value of type hcl.Expression, which can then be evaluated separately as described in expression-eval.