feat: add support for hcl files
This commit is contained in:
parent
14e0b29372
commit
8509f98d69
14 changed files with 113 additions and 5 deletions
|
@ -3,6 +3,7 @@
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
- Add support for TOML configuration files
|
- Add support for TOML configuration files
|
||||||
|
- Add support for HCL configuration files
|
||||||
- Use stdlib for tests instead of convey
|
- Use stdlib for tests instead of convey
|
||||||
- Update golangci-lint configuration
|
- Update golangci-lint configuration
|
||||||
|
|
||||||
|
|
56
adapters.go
56
adapters.go
|
@ -2,9 +2,14 @@ package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||||
|
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,6 +19,7 @@ const (
|
||||||
typeInvalid filetype = iota
|
typeInvalid filetype = iota
|
||||||
typeJSON
|
typeJSON
|
||||||
typeTOML
|
typeTOML
|
||||||
|
typeHCL
|
||||||
)
|
)
|
||||||
|
|
||||||
// getType returns the type of the config file.
|
// getType returns the type of the config file.
|
||||||
|
@ -23,18 +29,24 @@ func getType(filename string) filetype {
|
||||||
return typeJSON
|
return typeJSON
|
||||||
case strings.HasSuffix(filename, ".toml"):
|
case strings.HasSuffix(filename, ".toml"):
|
||||||
return typeTOML
|
return typeTOML
|
||||||
|
case strings.HasSuffix(filename, ".hcl"):
|
||||||
|
return typeHCL
|
||||||
default:
|
default:
|
||||||
return typeInvalid
|
return typeInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal unmarshals the given data to the given struct.
|
// unmarshal unmarshals the given data to the given struct.
|
||||||
func unmarshal(ft filetype, data []byte, v any) error {
|
func unmarshal(filepath string, data []byte, v any) error {
|
||||||
|
ft := getType(filepath)
|
||||||
|
|
||||||
switch ft {
|
switch ft {
|
||||||
case typeJSON:
|
case typeJSON:
|
||||||
return unmarshalJSON(data, v)
|
return unmarshalJSON(data, v)
|
||||||
case typeTOML:
|
case typeTOML:
|
||||||
return unmarshalTOML(data, v)
|
return unmarshalTOML(data, v)
|
||||||
|
case typeHCL:
|
||||||
|
return unmarshalHCL(filepath, data, v)
|
||||||
default:
|
default:
|
||||||
return ErrUnsupportedFileType
|
return ErrUnsupportedFileType
|
||||||
}
|
}
|
||||||
|
@ -47,6 +59,8 @@ func marshal(ft filetype, v any) ([]byte, error) {
|
||||||
return marshalJSON(v)
|
return marshalJSON(v)
|
||||||
case typeTOML:
|
case typeTOML:
|
||||||
return marshalTOML(v)
|
return marshalTOML(v)
|
||||||
|
case typeHCL:
|
||||||
|
return marshalHCL(v)
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnsupportedFileType
|
return nil, ErrUnsupportedFileType
|
||||||
}
|
}
|
||||||
|
@ -91,3 +105,43 @@ func marshalTOML(v any) ([]byte, error) {
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unmarshalHCL unmarshals the given data to the given struct.
|
||||||
|
func unmarshalHCL(filepath string, data []byte, v any) error {
|
||||||
|
err := hclsimple.Decode(filepath, data, nil, v)
|
||||||
|
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
|
||||||
|
errors.As(err, &diags)
|
||||||
|
newDiags := hclFilterDiagnostics(diags)
|
||||||
|
|
||||||
|
if len(newDiags) > 0 {
|
||||||
|
return fmt.Errorf("cannot parse config file: %w", newDiags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalHCL marshals the given struct to bytes.
|
||||||
|
func marshalHCL(v any) (b []byte, err error) { //nolint:nonamedreturns // need named return to convert a panic to error
|
||||||
|
f := hclwrite.NewEmptyFile()
|
||||||
|
gohcl.EncodeIntoBody(v, f.Body())
|
||||||
|
|
||||||
|
return f.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hclFilterDiagnostics(diags hcl.Diagnostics) hcl.Diagnostics {
|
||||||
|
var newDiags hcl.Diagnostics
|
||||||
|
|
||||||
|
for _, diag := range diags {
|
||||||
|
if diag.Summary != "Unsupported argument" {
|
||||||
|
newDiags = append(newDiags, diag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(newDiags) > 0 {
|
||||||
|
return newDiags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
// - JSON: ".json"
|
// - JSON: ".json"
|
||||||
// - TOML: ".toml"
|
// - TOML: ".toml"
|
||||||
|
// - HCL: ".hcl"
|
||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -89,7 +90,7 @@ func read(path string, data any) error {
|
||||||
return fmt.Errorf("cannot read config file: %w", err)
|
return fmt.Errorf("cannot read config file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return unmarshal(getType(path), content, data)
|
return unmarshal(path, content, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func write(path string, data any) error {
|
func write(path string, data any) error {
|
||||||
|
|
|
@ -23,6 +23,12 @@ func TestTOMLFiles(t *testing.T) {
|
||||||
runTestSuite(t, "toml")
|
runTestSuite(t, "toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHCLFiles(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
runTestSuite(t, "hcl")
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnknownFiles(t *testing.T) {
|
func TestUnknownFiles(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -75,9 +81,9 @@ func runTestSuite(t *testing.T, ext string) {
|
||||||
|
|
||||||
type testconf struct {
|
type testconf struct {
|
||||||
inUpdate func()
|
inUpdate func()
|
||||||
String string
|
String string `hcl:"String,optional"`
|
||||||
Invariant string
|
Invariant string `hcl:"Invariant,optional"`
|
||||||
Int int
|
Int int `hcl:"Int,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t testconf) Update() {
|
func (t testconf) Update() {
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -10,7 +10,18 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/agext/levenshtein v1.2.1 // indirect
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||||
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/hashicorp/hcl/v2 v2.23.0 // indirect
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/zclconf/go-cty v1.13.0 // indirect
|
||||||
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.11.0 // indirect
|
||||||
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -1,11 +1,33 @@
|
||||||
|
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
|
||||||
|
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||||
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
|
||||||
|
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
|
||||||
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
3
test_data/full.hcl
Normal file
3
test_data/full.hcl
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
String = "default string"
|
||||||
|
Invariant = "should not change"
|
||||||
|
Int = 1
|
1
test_data/invalid.hcl
Normal file
1
test_data/invalid.hcl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
String: not hcl
|
1
test_data/part1.hcl
Normal file
1
test_data/part1.hcl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
String = "foo"
|
1
test_data/part2.hcl
Normal file
1
test_data/part2.hcl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Int = 42
|
1
test_data/same1.hcl
Normal file
1
test_data/same1.hcl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
String = "foo"
|
1
test_data/same2.hcl
Normal file
1
test_data/same2.hcl
Normal file
|
@ -0,0 +1 @@
|
||||||
|
String = "bar"
|
3
test_data/unknown.hcl
Normal file
3
test_data/unknown.hcl
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
String = "config string"
|
||||||
|
Int = 42
|
||||||
|
Unknown = "foo"
|
2
test_data/valid.hcl
Normal file
2
test_data/valid.hcl
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
String = "config string"
|
||||||
|
Int = 42
|
Loading…
Reference in a new issue