Compare commits
5 commits
e72c317bbb
...
81dfdacb33
Author | SHA1 | Date | |
---|---|---|---|
81dfdacb33 | |||
8509f98d69 | |||
14e0b29372 | |||
57ff8f2911 | |||
15aa3ea21c |
15 changed files with 1298 additions and 784 deletions
1708
.golangci.yml
1708
.golangci.yml
File diff suppressed because it is too large
Load diff
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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)
|
||||||
|
|
||||||
|
|
73
adapters.go
73
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,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
|
||||||
|
}
|
||||||
|
|
63
config.go
63
config.go
|
@ -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 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
|
||||||
|
|
|
@ -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
23
go.mod
|
@ -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
39
go.sum
|
@ -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
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