Compare commits

..

No commits in common. "81dfdacb33d25d91bce7e2069e7119272f29b80c" and "e72c317bbb1b93d5418e2c6fce335362f032a5e0" have entirely different histories.

15 changed files with 788 additions and 1302 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,17 +3,8 @@
## 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
- Public functions now panic when the data to be marshaled or unmarshaled is not - Update golangci-lint config
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,14 +2,9 @@ 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"
) )
@ -19,55 +14,42 @@ 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
} }
} }
// unmarshal unmarshals the given data to the given struct. func unmarshal(ft filetype, data []byte, v interface{}) 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
} }
} }
// marshal marshals the given struct to bytes. func marshal(ft filetype, v interface{}) ([]byte, error) {
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
} }
} }
// unmarshalJSON unmarshals the given data to the given struct. func unmarshalJSON(data []byte, v interface{}) error {
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)
@ -76,8 +58,7 @@ func unmarshalJSON(data []byte, v any) error {
return nil return nil
} }
// marshalJSON marshals the given struct to bytes. func marshalJSON(v interface{}) ([]byte, error) {
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)
@ -86,8 +67,7 @@ func marshalJSON(v any) ([]byte, error) {
return data, nil return data, nil
} }
// unmarshalTOML unmarshals the given data to the given struct. func unmarshalTOML(data []byte, v interface{}) error {
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)
@ -96,8 +76,7 @@ func unmarshalTOML(data []byte, v any) error {
return nil return nil
} }
// marshalTOML marshals the given struct to bytes. func marshalTOML(v interface{}) ([]byte, error) {
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)
@ -105,43 +84,3 @@ 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
}

View file

@ -7,7 +7,6 @@
// //
// - JSON: ".json" // - JSON: ".json"
// - TOML: ".toml" // - TOML: ".toml"
// - HCL: ".hcl"
package conf package conf
import ( import (
@ -15,31 +14,15 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
) )
var ( // ErrUnsupportedFileType is returned when the type of the config file is not
// ErrUnsupportedFileType is returned when the type of the config file is // supported.
// not supported. var ErrUnsupportedFileType = errors.New("unsupported config type")
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)
} }
@ -49,10 +32,7 @@ func LoadFile(path string, data any) 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) {
@ -64,27 +44,21 @@ func LoadFiles(data any, 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 {
@ -101,36 +75,23 @@ func LoadAndUpdateFile(path string, data any) 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 any) error { func read(path string, data interface{}) 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(path, content, data) return unmarshal(getType(path), content, data)
}
func write(path string, data any) error {
v := reflect.Indirect(reflect.ValueOf(data))
if v.Kind() != reflect.Struct {
panic(ErrInvalidMarshalData)
} }
func write(path string, data interface{}) error {
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.net/go/conf" "code.bcarlin.xyz/go/conf"
) )
func TestJSONFiles(t *testing.T) { func TestJSONFiles(t *testing.T) {
@ -23,12 +23,6 @@ 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()
@ -81,9 +75,9 @@ func runTestSuite(t *testing.T, ext string) {
type testconf struct { type testconf struct {
inUpdate func() inUpdate func()
String string `hcl:"String,optional"` String string
Invariant string `hcl:"Invariant,optional"` Invariant string
Int int `hcl:"Int,optional"` Int int
} }
func (t testconf) Update() { func (t testconf) Update() {
@ -117,31 +111,6 @@ 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()
@ -231,31 +200,6 @@ 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)
})
})
}) })
} }
@ -295,9 +239,8 @@ 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)
assert.PanicsWithError(t, conf.ErrInvalidMarshalData.Error(), func() { err := conf.SaveFile(file, func() error { return nil })
conf.SaveFile(file, func() error { return nil }) require.Error(t, err)
})
assert.NoFileExists(t, file) assert.NoFileExists(t, file)
}) })
@ -488,32 +431,7 @@ 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, "the config file has not been updated") assert.True(t, 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,27 +1,12 @@
module code.bcarlin.net/go/conf module code.bcarlin.xyz/go/conf
go 1.22 go 1.17
toolchain go1.23.4 require github.com/stretchr/testify v1.9.0
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/google/go-cmp v0.6.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // 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,34 +1,21 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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