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
|
||||
|
||||
- Add support for TOML configuration files
|
||||
- Add support for HCL configuration files
|
||||
- Use stdlib for tests instead of convey
|
||||
- Update golangci-lint configuration
|
||||
|
||||
|
|
56
adapters.go
56
adapters.go
|
@ -2,9 +2,14 @@ package conf
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -14,6 +19,7 @@ const (
|
|||
typeInvalid filetype = iota
|
||||
typeJSON
|
||||
typeTOML
|
||||
typeHCL
|
||||
)
|
||||
|
||||
// getType returns the type of the config file.
|
||||
|
@ -23,18 +29,24 @@ func getType(filename string) filetype {
|
|||
return typeJSON
|
||||
case strings.HasSuffix(filename, ".toml"):
|
||||
return typeTOML
|
||||
case strings.HasSuffix(filename, ".hcl"):
|
||||
return typeHCL
|
||||
default:
|
||||
return typeInvalid
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
case typeJSON:
|
||||
return unmarshalJSON(data, v)
|
||||
case typeTOML:
|
||||
return unmarshalTOML(data, v)
|
||||
case typeHCL:
|
||||
return unmarshalHCL(filepath, data, v)
|
||||
default:
|
||||
return ErrUnsupportedFileType
|
||||
}
|
||||
|
@ -47,6 +59,8 @@ func marshal(ft filetype, v any) ([]byte, error) {
|
|||
return marshalJSON(v)
|
||||
case typeTOML:
|
||||
return marshalTOML(v)
|
||||
case typeHCL:
|
||||
return marshalHCL(v)
|
||||
default:
|
||||
return nil, ErrUnsupportedFileType
|
||||
}
|
||||
|
@ -91,3 +105,43 @@ func marshalTOML(v any) ([]byte, error) {
|
|||
|
||||
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"
|
||||
// - TOML: ".toml"
|
||||
// - HCL: ".hcl"
|
||||
package conf
|
||||
|
||||
import (
|
||||
|
@ -89,7 +90,7 @@ func read(path string, data any) error {
|
|||
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 {
|
||||
|
|
|
@ -23,6 +23,12 @@ func TestTOMLFiles(t *testing.T) {
|
|||
runTestSuite(t, "toml")
|
||||
}
|
||||
|
||||
func TestHCLFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
runTestSuite(t, "hcl")
|
||||
}
|
||||
|
||||
func TestUnknownFiles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -75,9 +81,9 @@ func runTestSuite(t *testing.T, ext string) {
|
|||
|
||||
type testconf struct {
|
||||
inUpdate func()
|
||||
String string
|
||||
Invariant string
|
||||
Int int
|
||||
String string `hcl:"String,optional"`
|
||||
Invariant string `hcl:"Invariant,optional"`
|
||||
Int int `hcl:"Int,optional"`
|
||||
}
|
||||
|
||||
func (t testconf) Update() {
|
||||
|
|
11
go.mod
11
go.mod
|
@ -10,7 +10,18 @@ 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/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/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
|
||||
)
|
||||
|
|
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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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