Categorygithub.com/hashicorp/hcl/v2
modulepackage
2.23.0
Repository: https://github.com/hashicorp/hcl.git
Documentation: pkg.go.dev

# 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.

# Packages

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 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 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 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 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.

# Functions

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.
ApplyPath is a helper function that applies a cty.Path to a value using the indexing and attribute access operations from HCL.
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.
EmptyBody returns a body with no content.
ExprAsKeyword attempts to interpret the given expression as a static keyword, returning the keyword string if possible, and the empty string if not.
ExprCall tests if the given expression is a function call and, if so, extracts the function name and the expressions that represent the arguments.
ExprList tests if the given expression is a static list construct and, if so, extracts the expressions that represent the list elements.
ExprMap tests if the given expression is a static map construct and, if so, extracts the expressions that represent the map elements.
GetAttr is a helper function that performs the same operation as the attribute access in the HCL expression language.
Index is a helper function that performs the same operation as the index operator in the HCL expression language.
MergeBodies is like MergeFiles except it deals directly with bodies, rather than with entire files.
MergeFiles combines the given files to produce a single body that contains configuration from all of the given files.
NewDiagnosticTextWriter creates a DiagnosticWriter that writes diagnostics to the given writer as formatted text.
NewRangeScanner creates a new RangeScanner for the given buffer, producing ranges for the given filename.
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.
RangeBetween returns a new range that spans from the beginning of the start range to the end of the end 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.
RelTraversalForExpr is similar to AbsTraversalForExpr but it returns a relative traversal instead.
StaticExpr returns an Expression that always evaluates to the given value.
TraversalJoin appends a relative traversal to an absolute traversal to produce a new absolute traversal.
UnwrapExpression removes any "wrapper" expressions from the given expression, to recover the representation of the physical expression given in source code.
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.

# Constants

DiagError indicates that the problem reported by a diagnostic prevents further progress in parsing and/or evaluating the subject.
DiagInvalid is the invalid zero value of DiagnosticSeverity.
DiagWarning indicates that the problem reported by a diagnostic warrants user attention but does not prevent further progress.

# Variables

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

# Structs

Attribute represents an attribute from within a body.
AttributeSchema represents the requirements for an attribute, and is used for matching attributes within bodies.
Block represents a nested block within a Body.
BlockHeaderSchema represents the shape of a block header, and is used for matching blocks within bodies.
BodyContent is the result of applying a BodySchema to a Body.
BodySchema represents the desired shallow structure of a body.
Diagnostic represents information to be presented to a user about an error or anomaly in parsing or evaluating configuration.
An EvalContext provides the variables and functions that should be used to evaluate an expression.
File is the top-level node that results from parsing a HCL file.
KeyValuePair represents a pair of expressions that serve as a single item within a map or object definition construct.
Pos represents a single position in a source file, by addressing the start byte of a unicode character encoded in UTF-8.
Range represents a span of characters between two positions in a source file.
RangeScanner is a helper that will scan over a buffer using a bufio.SplitFunc and visit a source range for each token matched.
StaticCall represents a function call that was extracted statically from an expression using ExprCall.
TraversalSplit represents a pair of traversals, the first of which is an absolute traversal and the second of which is relative to the first.
TraverseAttr looks up an attribute in its initial value.
TraverseIndex applies the index operation to its initial value.
TraverseRoot looks up a root name in a scope.
TraverseSplat applies the splat operation to its initial value.

# Interfaces

Body is a container for attributes and blocks.
DiagnosticExtraUnwrapper is an interface implemented by values in the Extra field of Diagnostic when they are wrapping another "Extra" value that was generated downstream.
A DiagnosticWriter emits diagnostics somehow.
Expression is a literal value or an expression provided in the configuration, which can be evaluated within a scope to produce a value.
A Traverser is a step within a Traversal.

# Type aliases

Attributes is a set of attributes keyed by their names.
Blocks is a sequence of Block.
Diagnostics is a list of Diagnostic instances.
DiagnosticSeverity represents the severity of a diagnostic.
A Traversal is a description of traversing through a value through a series of operations such as attribute lookup, index lookup, etc.