├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .golangci.yml ├── COPYING ├── README.md ├── bin ├── .go-1.24.3.pkg ├── .golangci-lint-2.1.6.pkg ├── README.hermit.md ├── activate-hermit ├── go ├── gofmt ├── golangci-lint ├── hermit └── hermit.hcl ├── go.mod ├── go.sum ├── marshal.go ├── marshal_test.go ├── parser.go ├── parser_test.go ├── renovate.json5 ├── schema.go ├── schema_test.go ├── unmarshal.go ├── unmarshal_test.go ├── util.go ├── util_test.go ├── visitor.go └── visitor_test.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alecthomas] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | name: CI 7 | jobs: 8 | test: 9 | name: Test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | - name: Init Hermit 15 | run: ./bin/hermit env -r >> $GITHUB_ENV 16 | - name: Test 17 | run: go test ./... 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | - name: Init Hermit 25 | run: ./bin/hermit env -r >> $GITHUB_ENV 26 | - name: golangci-lint 27 | run: golangci-lint run 28 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | tests: true 4 | timeout: 5m 5 | output: 6 | formats: 7 | text: 8 | print-issued-lines: false 9 | colors: true 10 | linters: 11 | default: all 12 | disable: 13 | - containedctx 14 | - contextcheck 15 | - cyclop 16 | - dogsled 17 | - dupl 18 | - dupword 19 | - err113 20 | - errchkjson 21 | - exhaustruct 22 | - exptostd 23 | - funlen 24 | - gochecknoglobals 25 | - gochecknoinits 26 | - gocognit 27 | - goconst 28 | - gocyclo 29 | - godot 30 | - godox 31 | - gomoddirectives 32 | - goprintffuncname 33 | - interfacebloat 34 | - ireturn 35 | - lll 36 | - mnd 37 | - musttag 38 | - nestif 39 | - nilerr 40 | - nilnil 41 | - nlreturn 42 | - nolintlint 43 | - nonamedreturns 44 | - nosprintfhostport 45 | - paralleltest 46 | - perfsprint 47 | - prealloc 48 | - protogetter 49 | - recvcheck 50 | - rowserrcheck 51 | - sqlclosecheck 52 | - tagalign 53 | - tagliatelle 54 | - testpackage 55 | - thelper 56 | - varnamelen 57 | - wastedassign 58 | - whitespace 59 | - wsl 60 | - wrapcheck 61 | - funcorder 62 | - maintidx 63 | - forcetypeassert 64 | - errcheck 65 | - errorlint 66 | 67 | settings: 68 | wrapcheck: 69 | ignore-sigs: 70 | - errors.New 71 | - errors.Errorf 72 | - errors.Join 73 | - errors.Wrap 74 | - errors.Wrapf 75 | - errors.WithStack 76 | - errors.WithStack2 77 | - errors.WithStack3 78 | depguard: 79 | rules: 80 | main: 81 | deny: 82 | - pkg: github.com/pkg/errors 83 | desc: use github.com/alecthomas/errors 84 | - pkg: github.com/stretchr/testify 85 | desc: use github.com/alecthomas/assert/v2 86 | - pkg: errors 87 | desc: use github.com/alecthomas/errors 88 | - pkg: braces.dev/errtrace 89 | desc: use github.com/alecthomas/errors 90 | - pkg: os/exec 91 | desc: use github.com/block/ftl/internal/exec 92 | - pkg: golang.design/x/reflect 93 | desc: use github.com/block/ftl/common/reflect 94 | - pkg: github.com/reugn/go-quartz/logger 95 | desc: use github.com/block/ftl/internal/log 96 | dupl: 97 | threshold: 100 98 | errcheck: 99 | check-blank: true 100 | exhaustive: 101 | default-signifies-exhaustive: true 102 | goconst: 103 | min-len: 8 104 | min-occurrences: 3 105 | gocritic: 106 | disabled-checks: 107 | - ifElseChain 108 | gocyclo: 109 | min-complexity: 20 110 | govet: 111 | enable: 112 | - shadow 113 | spancheck: 114 | extra-start-span-signatures: 115 | - github.com/block/ftl/backend/controller/observability.BeginSpan:opentelemetry 116 | # exhaustruct: 117 | # include: 118 | # - '^github.com/block/ftl/common/schema\.Module$' 119 | exclusions: 120 | generated: lax 121 | rules: 122 | - path: (.+)\.go$ 123 | text: "^(G104|G204):" 124 | - path: (.+)\.go$ 125 | text: Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked 126 | - path: (.+)\.go$ 127 | text: "internal error: no range for" 128 | - path: (.+)\.go$ 129 | text: exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported 130 | - path: (.+)\.go$ 131 | text: composite literal uses unkeyed fields 132 | - path: (.+)\.go$ 133 | text: declaration of "err" shadows declaration 134 | - path: (.+)\.go$ 135 | text: by other packages, and that stutters 136 | - path: (.+)\.go$ 137 | text: Potential file inclusion via variable 138 | - path: (.+)\.go$ 139 | text: at least one file in a package should have a package comment 140 | - path: (.+)\.go$ 141 | text: bad syntax for struct tag pair 142 | - path: (.+)\.go$ 143 | text: should have comment or be unexported 144 | - path: (.+)\.go$ 145 | text: package-comments 146 | - path: (.+)\.go$ 147 | text: parameter testing.TB should have name tb 148 | - path: (.+)\.go$ 149 | text: blank-imports 150 | - path: (.+)\.go$ 151 | text: should have comment \(or a comment on this block\) or be unexported 152 | - path: (.+)\.go$ 153 | text: caseOrder 154 | - path: (.+)\.go$ 155 | text: unused-parameter 156 | - path: (.+)\.go$ 157 | text: "^loopclosure:" 158 | - path: (.+)\.go$ 159 | text: 'shadow: declaration of "ctx" shadows declaration at' 160 | - path: (.+)\.go$ 161 | text: 'shadow: declaration of "ok" shadows declaration' 162 | - path: (.+)\.go$ 163 | text: "^dot-imports:" 164 | - path: (.+)\.go$ 165 | text: fmt.Errorf can be replaced with errors.New 166 | - path: (.+)\.go$ 167 | text: fmt.Sprintf can be replaced with string concatenation 168 | - path: (.+)\.go$ 169 | text: strings.Title has been deprecated 170 | - path: (.+)\.go$ 171 | text: error returned from external package is unwrapped.*TranslatePGError 172 | - path: (.+)\.go$ 173 | text: struct literal uses unkeyed fields 174 | - path: (.+)\.go$ 175 | text: "exported: comment on exported type" 176 | - path: (.+)\.go$ 177 | text: result .* \(error\) is always nil 178 | - path: (.+)\.go$ 179 | text: QF1001 180 | paths: 181 | - cmd/protopkg/main.go 182 | - resources 183 | - old 184 | - third_party$ 185 | - builtin$ 186 | - examples$ 187 | issues: 188 | max-issues-per-linter: 0 189 | max-same-issues: 0 190 | formatters: 191 | exclusions: 192 | generated: lax 193 | paths: 194 | - third_party$ 195 | - builtin$ 196 | - examples$ 197 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Alec Thomas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Parsing, encoding and decoding of HCL to and from Go types 2 | 3 | [![](https://godoc.org/github.com/alecthomas/hcl/v2?status.svg)](http://godoc.org/github.com/alecthomas/hcl/v2) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/hcl.svg)](https://circleci.com/gh/alecthomas/hcl) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/hcl/v2)](https://goreportcard.com/report/github.com/alecthomas/hcl/v2) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) 4 | 5 | This package provides idiomatic Go functions for marshalling and unmarshalling HCL, as well as an AST 6 | 7 | It supports the same tags as the Hashicorp [hcl2](https://github.com/hashicorp/hcl/tree/hcl2) 8 | `gohcl` package, but is much less complex. 9 | 10 | Unlike `gohcl` it also natively supports `time.Duration`, `time.Time`, `encoding.TextUnmarshaler` 11 | and `json.Unmarshaler`. 12 | 13 | It is HCL1 compatible and does not support any HCL2 specific features. 14 | 15 | ## Design 16 | 17 | HCL -> AST -> Go -> AST -> HCL 18 | 19 | Mapping can start from any point in this cycle. 20 | 21 | Marshalling, unmarshalling, parsing and serialisation are all structurally isomorphic operations. That is, HCL can be 22 | deserialised into an AST or Go, or vice versa, and the structure on both ends will be identical. 23 | 24 | HCL is always parsed into an AST before unmarshaling and, similarly, Go structures are always mapped to an AST before 25 | being serialised to HCL. 26 | 27 | Between | And | Preserves 28 | -----------------|--------------|----------------- 29 | HCL | AST | Structure, values, order, comments. 30 | HCL | Go | Structure, values, partial comments (via the `help:""` tag). 31 | AST | Go | Structure, values. 32 | 33 | ## Schema reflection 34 | 35 | HCL has no real concept of schemas (that I can find), but there is precedent for something similar in Terraform variable 36 | definition files. This package supports reflecting a rudimentary schema from Go, where the value for each attribute is 37 | one of the scalar types `number`, `string` or `boolean`. Lists and maps are typed by example. 38 | 39 | Here's an example schema. 40 | 41 | ``` 42 | // A string field. 43 | str = string 44 | num = number 45 | bool = boolean 46 | list = [string] 47 | 48 | // A map. 49 | map = { 50 | string: number, 51 | } 52 | 53 | // A block. 54 | block "name" { 55 | attr = string 56 | } 57 | 58 | // Repeated blocks. 59 | block_slice "label0" "label1" { 60 | attr = string 61 | } 62 | ``` 63 | 64 | Comments are from `help:""` tags. See [schema_test.go](https://github.com/alecthomas/hcl/blob/master/schema_test.go) for 65 | details. 66 | 67 | ## Struct field tags 68 | 69 | The tag format is as with other similar serialisation packages: 70 | 71 | ``` 72 | hcl:"[][,