Compare commits
No commits in common. "81dfdacb33d25d91bce7e2069e7119272f29b80c" and "e72c317bbb1b93d5418e2c6fce335362f032a5e0" have entirely different histories.
81dfdacb33
...
e72c317bbb
15 changed files with 788 additions and 1302 deletions
1754
.golangci.yml
1754
.golangci.yml
File diff suppressed because it is too large
Load diff
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
73
adapters.go
73
adapters.go
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
63
config.go
63
config.go
|
@ -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 {
|
func write(path string, data interface{}) 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
|
||||||
|
|
|
@ -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
23
go.mod
|
@ -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
39
go.sum
|
@ -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=
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
String = "default string"
|
|
||||||
Invariant = "should not change"
|
|
||||||
Int = 1
|
|
|
@ -1 +0,0 @@
|
||||||
String: not hcl
|
|
|
@ -1 +0,0 @@
|
||||||
String = "foo"
|
|
|
@ -1 +0,0 @@
|
||||||
Int = 42
|
|
|
@ -1 +0,0 @@
|
||||||
String = "foo"
|
|
|
@ -1 +0,0 @@
|
||||||
String = "bar"
|
|
|
@ -1,3 +0,0 @@
|
||||||
String = "config string"
|
|
||||||
Int = 42
|
|
||||||
Unknown = "foo"
|
|
|
@ -1,2 +0,0 @@
|
||||||
String = "config string"
|
|
||||||
Int = 42
|
|
Loading…
Add table
Reference in a new issue