Compare commits

..

5 commits

Author SHA1 Message Date
81dfdacb33
feat: Load* and Save functions panic if data is not a pointer to a struct
All checks were successful
/ linting (push) Successful in 4m33s
/ tests (push) Successful in 56s
2025-01-15 01:50:41 +01:00
8509f98d69
feat: add support for hcl files 2025-01-15 01:49:44 +01:00
14e0b29372
chore: update changelog 2025-01-15 01:47:06 +01:00
57ff8f2911
chore: update golangci-lint config 2025-01-15 01:46:50 +01:00
15aa3ea21c
chore: update go.mod and dependencies 2025-01-14 01:00:34 +01:00
15 changed files with 1298 additions and 784 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,17 @@
## 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 config - Public functions now panic when the data to be marshaled or unmarshaled is not
a pointer to a struct. These errors should be caught during deelopment (with
unit tests).
- Update golangci-lint configuration
## v0.3.0 (2025-01-14)
- The repo has moded from code.bcarlin.xyz/go/conf to code.bcarlin.net/go/conf.
The old module has been deprecated in favor of the new one.
## v0.2.0 (2021-12-19) ## v0.2.0 (2021-12-19)

View file

@ -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,42 +19,55 @@ const (
typeInvalid filetype = iota typeInvalid filetype = iota
typeJSON typeJSON
typeTOML typeTOML
typeHCL
) )
// getType returns the type of the config file.
func getType(filename string) filetype { func getType(filename string) filetype {
switch { switch {
case strings.HasSuffix(filename, ".json"): case strings.HasSuffix(filename, ".json"):
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
} }
} }
func unmarshal(ft filetype, data []byte, v interface{}) error { // unmarshal unmarshals the given data to the given struct.
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
} }
} }
func marshal(ft filetype, v interface{}) ([]byte, error) { // marshal marshals the given struct to bytes.
func marshal(ft filetype, v any) ([]byte, error) {
switch ft { switch ft {
case typeJSON: case typeJSON:
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
} }
} }
func unmarshalJSON(data []byte, v interface{}) error { // unmarshalJSON unmarshals the given data to the given struct.
func unmarshalJSON(data []byte, v any) error {
err := json.Unmarshal(data, v) err := json.Unmarshal(data, v)
if err != nil { if err != nil {
return fmt.Errorf("cannot parse config file: %w", err) return fmt.Errorf("cannot parse config file: %w", err)
@ -58,7 +76,8 @@ func unmarshalJSON(data []byte, v interface{}) error {
return nil return nil
} }
func marshalJSON(v interface{}) ([]byte, error) { // marshalJSON marshals the given struct to bytes.
func marshalJSON(v any) ([]byte, error) {
data, err := json.MarshalIndent(v, "", " ") data, err := json.MarshalIndent(v, "", " ")
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot generate config content: %w", err) return nil, fmt.Errorf("cannot generate config content: %w", err)
@ -67,7 +86,8 @@ func marshalJSON(v interface{}) ([]byte, error) {
return data, nil return data, nil
} }
func unmarshalTOML(data []byte, v interface{}) error { // unmarshalTOML unmarshals the given data to the given struct.
func unmarshalTOML(data []byte, v any) error {
err := toml.Unmarshal(data, v) err := toml.Unmarshal(data, v)
if err != nil { if err != nil {
return fmt.Errorf("cannot parse config file: %w", err) return fmt.Errorf("cannot parse config file: %w", err)
@ -76,7 +96,8 @@ func unmarshalTOML(data []byte, v interface{}) error {
return nil return nil
} }
func marshalTOML(v interface{}) ([]byte, error) { // marshalTOML marshals the given struct to bytes.
func marshalTOML(v any) ([]byte, error) {
data, err := toml.Marshal(v) data, err := toml.Marshal(v)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot generate config content: %w", err) return nil, fmt.Errorf("cannot generate config content: %w", err)
@ -84,3 +105,43 @@ func marshalTOML(v interface{}) ([]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
}

View file

@ -7,6 +7,7 @@
// //
// - JSON: ".json" // - JSON: ".json"
// - TOML: ".toml" // - TOML: ".toml"
// - HCL: ".hcl"
package conf package conf
import ( import (
@ -14,15 +15,31 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
) )
// ErrUnsupportedFileType is returned when the type of the config file is not var (
// supported. // ErrUnsupportedFileType is returned when the type of the config file is
var ErrUnsupportedFileType = errors.New("unsupported config type") // not supported.
ErrUnsupportedFileType = errors.New("unsupported config type")
// ErrInvalidMarshalData is returned when the marshaled value is not a
// struct.
ErrInvalidMarshalData = errors.New("the marshaled value must be a struct")
// ErrInvalidUnmarshalData is returned when the marshaled value is not a
// struct.
ErrInvalidUnmarshalData = errors.New(
"the unmarshaled value must be a pointer to a struct",
)
)
// LoadFile reads the file at path, parses its json content and fills the struct // LoadFile reads the file at path, parses its json content and fills the struct
// with the content of the file. // with the content of the file.
func LoadFile(path string, data interface{}) error { //
// LoadFile panics with [ErrInvalidUnmarshalData] if data is not a pointer to a
// struct, as this error should be caught during dev.
func LoadFile(path string, data any) error {
return read(path, data) return read(path, data)
} }
@ -32,7 +49,10 @@ func LoadFile(path string, data interface{}) error {
// //
// It returns an error only if the content of a file is invalid, i.e. it // It returns an error only if the content of a file is invalid, i.e. it
// cannot be unmarshaled to the struct. // cannot be unmarshaled to the struct.
func LoadFiles(data interface{}, paths ...string) error { //
// LoadFiles panics with [ErrInvalidUnmarshalData] if data is not a pointer to a
// struct, as this error should be caught during dev.
func LoadFiles(data any, paths ...string) error {
for _, p := range paths { for _, p := range paths {
err := read(p, data) err := read(p, data)
if err != nil && !errors.Is(err, os.ErrNotExist) { if err != nil && !errors.Is(err, os.ErrNotExist) {
@ -44,21 +64,27 @@ func LoadFiles(data interface{}, paths ...string) error {
} }
// SaveFile writes the given data serialized in JSON in the given path. // SaveFile writes the given data serialized in JSON in the given path.
func SaveFile(path string, data interface{}) error { //
// SaveFile panics with [ErrInvalidMarshalData] if data is not a struct, as
// this error should be caught during dev.
func SaveFile(path string, data any) error {
return write(path, data) return write(path, data)
} }
// LoadAndUpdateFile reads the config fileat path and // LoadAndUpdateFile reads the config fileat path and
// updates it, meaning that it adds new options, removes // updates it, meaning that it adds new options, removes
// old ones, and update it by calling the Update method of // old ones, and update it by calling the Update method of
// data if it implements the interface Updater. // data if it implements the interface [Updater].
// //
// If no file is found at path, it is created and // If no file is found at path, it is created and
// initialized with the default values. // initialized with the default values.
// //
// An error is returned only if the config file cannot be // An error is returned only if the config file cannot be
// written. // written.
func LoadAndUpdateFile(path string, data interface{}) error { //
// LoadAndUpdateFile panics with [ErrInvalidUnmarshalData] if data is not a
// pointer to a struct, as this error should be caught during dev.
func LoadAndUpdateFile(path string, data any) error {
if _, err := os.Stat(path); !os.IsNotExist(err) { if _, err := os.Stat(path); !os.IsNotExist(err) {
err2 := read(path, data) err2 := read(path, data)
if err2 != nil { if err2 != nil {
@ -75,23 +101,36 @@ func LoadAndUpdateFile(path string, data interface{}) error {
// Updater is the interface that can be implemented by // Updater is the interface that can be implemented by
// config structs. If it is implemented, Update() is // config structs. If it is implemented, Update() is
// called by LoadAndUpdateFile(). It allows one to modify // called by [LoadAndUpdateFile]. It allows one to modify
// the data and persist those changes, for example to // the data and persist those changes, for example to
// change default values. // change default values.
type Updater interface { type Updater interface {
// Update is called by LoadAndUpdateFile
Update() Update()
} }
func read(path string, data interface{}) error { func read(path string, data any) error {
val := reflect.ValueOf(data)
indVal := reflect.Indirect(val)
if val.Kind() != reflect.Ptr || indVal.Kind() != reflect.Struct {
panic(ErrInvalidUnmarshalData)
}
content, err := os.ReadFile(filepath.Clean(path)) content, err := os.ReadFile(filepath.Clean(path))
if err != nil { if err != nil {
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 interface{}) error { func write(path string, data any) error {
v := reflect.Indirect(reflect.ValueOf(data))
if v.Kind() != reflect.Struct {
panic(ErrInvalidMarshalData)
}
content, err := marshal(getType(path), data) content, err := marshal(getType(path), data)
if err != nil { if err != nil {
return err return err

View file

@ -8,7 +8,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"code.bcarlin.xyz/go/conf" "code.bcarlin.net/go/conf"
) )
func TestJSONFiles(t *testing.T) { func TestJSONFiles(t *testing.T) {
@ -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() {
@ -111,6 +117,31 @@ func testLoadFile(t *testing.T, ext string) {
assert.Equal(t, "should not change", c.Invariant) assert.Equal(t, "should not change", c.Invariant)
}) })
t.Run("with a valid file and invalid data", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadFile(file, func() {})
})
})
t.Run("with a valid file and data is not a pointer", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
c := testconf{
String: "default string",
Int: 1,
Invariant: "should not change",
}
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadFile(file, c)
})
})
t.Run("with an invalid file", func(t *testing.T) { t.Run("with an invalid file", func(t *testing.T) {
t.Parallel() t.Parallel()
@ -200,6 +231,31 @@ func testLoadFiles(t *testing.T, ext string) {
assert.Equal(t, "", c.String) assert.Equal(t, "", c.String)
}) })
t.Run("with a valid file and invalid data", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadFiles(func() {}, file)
})
})
t.Run("with a valid file and data is not a pointer", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
c := testconf{
String: "default string",
Int: 1,
Invariant: "should not change",
}
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadFiles(c, file)
})
})
}) })
} }
@ -239,8 +295,9 @@ func testSaveFile(t *testing.T, ext string) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
file := filepath.Join(tmpDir, "test."+ext) file := filepath.Join(tmpDir, "test."+ext)
err := conf.SaveFile(file, func() error { return nil }) assert.PanicsWithError(t, conf.ErrInvalidMarshalData.Error(), func() {
require.Error(t, err) conf.SaveFile(file, func() error { return nil })
})
assert.NoFileExists(t, file) assert.NoFileExists(t, file)
}) })
@ -431,7 +488,32 @@ func testLoadAndUpdateFile(t *testing.T, ext string) {
require.NoError(t, err) require.NoError(t, err)
assert.NotContains(t, string(newContent), "Unknown") assert.NotContains(t, string(newContent), "Unknown")
assert.True(t, updated) assert.True(t, updated, "the config file has not been updated")
})
t.Run("with a valid file and invalid data", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadAndUpdateFile(file, func() {})
})
})
t.Run("with a valid file and data is not a pointer", func(t *testing.T) {
t.Parallel()
file := "test_data/valid." + ext
c := testconf{
String: "default string",
Int: 1,
Invariant: "should not change",
}
assert.PanicsWithError(t, conf.ErrInvalidUnmarshalData.Error(), func() {
conf.LoadAndUpdateFile(file, c)
})
}) })
}) })
} }

23
go.mod
View file

@ -1,12 +1,27 @@
module code.bcarlin.xyz/go/conf module code.bcarlin.net/go/conf
go 1.17 go 1.22
require github.com/stretchr/testify v1.9.0 toolchain go1.23.4
require ( require (
github.com/pelletier/go-toml/v2 v2.2.3
github.com/stretchr/testify v1.10.0
)
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/pelletier/go-toml/v2 v2.2.2 // 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
) )

39
go.sum
View file

@ -1,21 +1,34 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 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 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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

3
test_data/full.hcl Normal file
View file

@ -0,0 +1,3 @@
String = "default string"
Invariant = "should not change"
Int = 1

1
test_data/invalid.hcl Normal file
View file

@ -0,0 +1 @@
String: not hcl

1
test_data/part1.hcl Normal file
View file

@ -0,0 +1 @@
String = "foo"

1
test_data/part2.hcl Normal file
View file

@ -0,0 +1 @@
Int = 42

1
test_data/same1.hcl Normal file
View file

@ -0,0 +1 @@
String = "foo"

1
test_data/same2.hcl Normal file
View file

@ -0,0 +1 @@
String = "bar"

3
test_data/unknown.hcl Normal file
View file

@ -0,0 +1,3 @@
String = "config string"
Int = 42
Unknown = "foo"

2
test_data/valid.hcl Normal file
View file

@ -0,0 +1,2 @@
String = "config string"
Int = 42