diff --git a/go.mod b/go.mod index 50ff1a3d8..d205c68a9 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,6 @@ go 1.21 require ( github.com/fsnotify/fsnotify v1.7.0 github.com/go-viper/mapstructure/v2 v2.0.0 - github.com/hashicorp/hcl v1.0.0 - github.com/magiconair/properties v1.8.7 github.com/pelletier/go-toml/v2 v2.2.2 github.com/sagikazarmark/locafero v0.6.0 github.com/spf13/afero v1.11.0 @@ -14,7 +12,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 github.com/subosito/gotenv v1.6.0 - gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index da9f1f046..c8524c854 100644 --- a/go.sum +++ b/go.sum @@ -9,14 +9,10 @@ github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAp github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -56,8 +52,6 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/encoding/hcl/codec.go b/internal/encoding/hcl/codec.go deleted file mode 100644 index d7fa8a1b7..000000000 --- a/internal/encoding/hcl/codec.go +++ /dev/null @@ -1,40 +0,0 @@ -package hcl - -import ( - "bytes" - "encoding/json" - - "github.com/hashicorp/hcl" - "github.com/hashicorp/hcl/hcl/printer" -) - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding. -// TODO: add printer config to the codec? -type Codec struct{} - -func (Codec) Encode(v map[string]any) ([]byte, error) { - b, err := json.Marshal(v) - if err != nil { - return nil, err - } - - // TODO: use printer.Format? Is the trailing newline an issue? - - ast, err := hcl.Parse(string(b)) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - - err = printer.Fprint(&buf, ast.Node) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (Codec) Decode(b []byte, v map[string]any) error { - return hcl.Unmarshal(b, &v) -} diff --git a/internal/encoding/hcl/codec_test.go b/internal/encoding/hcl/codec_test.go deleted file mode 100644 index b1d2d5a8a..000000000 --- a/internal/encoding/hcl/codec_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package hcl - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `# key-value pair -"key" = "value" - -// list -"list" = ["item1", "item2", "item3"] - -/* map */ -"map" = { - "key" = "value" -} - -/* -nested map -*/ -"nested_map" "map" { - "key" = "value" - - "list" = ["item1", "item2", "item3"] -}` - -// encoded form of the data. -const encoded = `"key" = "value" - -"list" = ["item1", "item2", "item3"] - -"map" = { - "key" = "value" -} - -"nested_map" "map" { - "key" = "value" - - "list" = ["item1", "item2", "item3"] -}` - -// decoded form of the data. -// -// In case of HCL it's slightly different from Viper's internal representation -// (e.g. map is decoded into a list of maps). -var decoded = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": []map[string]any{ - { - "key": "value", - }, - }, - "nested_map": []map[string]any{ - { - "map": []map[string]any{ - { - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, - }, - }, -} - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - "map": map[string]any{ - "key": "value", - }, - "nested_map": map[string]any{ - "map": map[string]any{ - "key": "value", - "list": []any{ - "item1", - "item2", - "item3", - }, - }, - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, decoded, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/ini/codec.go b/internal/encoding/ini/codec.go deleted file mode 100644 index d91cf59d2..000000000 --- a/internal/encoding/ini/codec.go +++ /dev/null @@ -1,99 +0,0 @@ -package ini - -import ( - "bytes" - "sort" - "strings" - - "github.com/spf13/cast" - "gopkg.in/ini.v1" -) - -// LoadOptions contains all customized options used for load data source(s). -// This type is added here for convenience: this way consumers can import a single package called "ini". -type LoadOptions = ini.LoadOptions - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding. -type Codec struct { - KeyDelimiter string - LoadOptions LoadOptions -} - -func (c Codec) Encode(v map[string]any) ([]byte, error) { - cfg := ini.Empty() - ini.PrettyFormat = false - - flattened := map[string]any{} - - flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) - - keys := make([]string, 0, len(flattened)) - - for key := range flattened { - keys = append(keys, key) - } - - sort.Strings(keys) - - for _, key := range keys { - sectionName, keyName := "", key - - lastSep := strings.LastIndex(key, ".") - if lastSep != -1 { - sectionName = key[:(lastSep)] - keyName = key[(lastSep + 1):] - } - - // TODO: is this a good idea? - if sectionName == "default" { - sectionName = "" - } - - cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key])) - } - - var buf bytes.Buffer - - _, err := cfg.WriteTo(&buf) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c Codec) Decode(b []byte, v map[string]any) error { - cfg := ini.Empty(c.LoadOptions) - - err := cfg.Append(b) - if err != nil { - return err - } - - sections := cfg.Sections() - - for i := 0; i < len(sections); i++ { - section := sections[i] - keys := section.Keys() - - for j := 0; j < len(keys); j++ { - key := keys[j] - value := cfg.Section(section.Name()).Key(key.Name()).String() - - deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter())) - - // set innermost value - deepestMap[key.Name()] = value - } - } - - return nil -} - -func (c Codec) keyDelimiter() string { - if c.KeyDelimiter == "" { - return "." - } - - return c.KeyDelimiter -} diff --git a/internal/encoding/ini/codec_test.go b/internal/encoding/ini/codec_test.go deleted file mode 100644 index f9d9e7019..000000000 --- a/internal/encoding/ini/codec_test.go +++ /dev/null @@ -1,99 +0,0 @@ -package ini - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `; key-value pair -key=value ; key-value pair - -# map -[map] # map -key=%(key)s - -` - -// encoded form of the data. -const encoded = `key=value - -[map] -key=value -` - -// decoded form of the data. -// -// In case of INI it's slightly different from Viper's internal representation -// (e.g. top level keys land in a section called default). -var decoded = map[string]any{ - "DEFAULT": map[string]any{ - "key": "value", - }, - "map": map[string]any{ - "key": "value", - }, -} - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "map": map[string]any{ - "key": "value", - }, -} - -func TestCodec_Encode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) - }) - - t.Run("Default", func(t *testing.T) { - codec := Codec{} - - data := map[string]any{ - "default": map[string]any{ - "key": "value", - }, - "map": map[string]any{ - "key": "value", - }, - } - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) - }) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, decoded, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(`invalid data`), v) - require.Error(t, err) - - t.Logf("decoding failed as expected: %s", err) - }) -} diff --git a/internal/encoding/ini/map_utils.go b/internal/encoding/ini/map_utils.go deleted file mode 100644 index 490ab594e..000000000 --- a/internal/encoding/ini/map_utils.go +++ /dev/null @@ -1,74 +0,0 @@ -package ini - -import ( - "strings" - - "github.com/spf13/cast" -) - -// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED -// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE -// deepSearch scans deep maps, following the key indexes listed in the -// sequence "path". -// The last value is expected to be another map, and is returned. -// -// In case intermediate keys do not exist, or map to a non-map value, -// a new map is created and inserted, and the search continues from there: -// the initial map "m" may be modified! -func deepSearch(m map[string]any, path []string) map[string]any { - for _, k := range path { - m2, ok := m[k] - if !ok { - // intermediate key does not exist - // => create it and continue from there - m3 := make(map[string]any) - m[k] = m3 - m = m3 - continue - } - m3, ok := m2.(map[string]any) - if !ok { - // intermediate key is a value - // => replace with a new map - m3 = make(map[string]any) - m[k] = m3 - } - // continue search from here - m = m3 - } - return m -} - -// flattenAndMergeMap recursively flattens the given map into a new map -// Code is based on the function with the same name in the main package. -// TODO: move it to a common place. -func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { - if shadow != nil && prefix != "" && shadow[prefix] != nil { - // prefix is shadowed => nothing more to flatten - return shadow - } - if shadow == nil { - shadow = make(map[string]any) - } - - var m2 map[string]any - if prefix != "" { - prefix += delimiter - } - for k, val := range m { - fullKey := prefix + k - switch val := val.(type) { - case map[string]any: - m2 = val - case map[any]any: - m2 = cast.ToStringMap(val) - default: - // immediate value - shadow[strings.ToLower(fullKey)] = val - continue - } - // recursively merge to shadow map - shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) - } - return shadow -} diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go deleted file mode 100644 index e92e5172c..000000000 --- a/internal/encoding/javaproperties/codec.go +++ /dev/null @@ -1,86 +0,0 @@ -package javaproperties - -import ( - "bytes" - "sort" - "strings" - - "github.com/magiconair/properties" - "github.com/spf13/cast" -) - -// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding. -type Codec struct { - KeyDelimiter string - - // Store read properties on the object so that we can write back in order with comments. - // This will only be used if the configuration read is a properties file. - // TODO: drop this feature in v2 - // TODO: make use of the global properties object optional - Properties *properties.Properties -} - -func (c *Codec) Encode(v map[string]any) ([]byte, error) { - if c.Properties == nil { - c.Properties = properties.NewProperties() - } - - flattened := map[string]any{} - - flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) - - keys := make([]string, 0, len(flattened)) - - for key := range flattened { - keys = append(keys, key) - } - - sort.Strings(keys) - - for _, key := range keys { - _, _, err := c.Properties.Set(key, cast.ToString(flattened[key])) - if err != nil { - return nil, err - } - } - - var buf bytes.Buffer - - _, err := c.Properties.WriteComment(&buf, "#", properties.UTF8) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func (c *Codec) Decode(b []byte, v map[string]any) error { - var err error - c.Properties, err = properties.Load(b, properties.UTF8) - if err != nil { - return err - } - - for _, key := range c.Properties.Keys() { - // ignore existence check: we know it's there - value, _ := c.Properties.Get(key) - - // recursively build nested maps - path := strings.Split(key, c.keyDelimiter()) - lastKey := strings.ToLower(path[len(path)-1]) - deepestMap := deepSearch(v, path[0:len(path)-1]) - - // set innermost value - deepestMap[lastKey] = value - } - - return nil -} - -func (c Codec) keyDelimiter() string { - if c.KeyDelimiter == "" { - return "." - } - - return c.KeyDelimiter -} diff --git a/internal/encoding/javaproperties/codec_test.go b/internal/encoding/javaproperties/codec_test.go deleted file mode 100644 index ab5aec0b5..000000000 --- a/internal/encoding/javaproperties/codec_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package javaproperties - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// original form of the data. -const original = `#key-value pair -key = value -map.key = value -` - -// encoded form of the data. -const encoded = `key = value -map.key = value -` - -// data is Viper's internal representation. -var data = map[string]any{ - "key": "value", - "map": map[string]any{ - "key": "value", - }, -} - -func TestCodec_Encode(t *testing.T) { - codec := Codec{} - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, encoded, string(b)) -} - -func TestCodec_Decode(t *testing.T) { - t.Run("OK", func(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - assert.Equal(t, data, v) - }) - - t.Run("InvalidData", func(t *testing.T) { - t.Skip("TODO: needs invalid data example") - - codec := Codec{} - - v := map[string]any{} - - codec.Decode([]byte(``), v) - - assert.Empty(t, v) - }) -} - -func TestCodec_DecodeEncode(t *testing.T) { - codec := Codec{} - - v := map[string]any{} - - err := codec.Decode([]byte(original), v) - require.NoError(t, err) - - b, err := codec.Encode(data) - require.NoError(t, err) - - assert.Equal(t, original, string(b)) -} diff --git a/internal/encoding/javaproperties/map_utils.go b/internal/encoding/javaproperties/map_utils.go deleted file mode 100644 index 6e1aff223..000000000 --- a/internal/encoding/javaproperties/map_utils.go +++ /dev/null @@ -1,74 +0,0 @@ -package javaproperties - -import ( - "strings" - - "github.com/spf13/cast" -) - -// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED -// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE -// deepSearch scans deep maps, following the key indexes listed in the -// sequence "path". -// The last value is expected to be another map, and is returned. -// -// In case intermediate keys do not exist, or map to a non-map value, -// a new map is created and inserted, and the search continues from there: -// the initial map "m" may be modified! -func deepSearch(m map[string]any, path []string) map[string]any { - for _, k := range path { - m2, ok := m[k] - if !ok { - // intermediate key does not exist - // => create it and continue from there - m3 := make(map[string]any) - m[k] = m3 - m = m3 - continue - } - m3, ok := m2.(map[string]any) - if !ok { - // intermediate key is a value - // => replace with a new map - m3 = make(map[string]any) - m[k] = m3 - } - // continue search from here - m = m3 - } - return m -} - -// flattenAndMergeMap recursively flattens the given map into a new map -// Code is based on the function with the same name in the main package. -// TODO: move it to a common place. -func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { - if shadow != nil && prefix != "" && shadow[prefix] != nil { - // prefix is shadowed => nothing more to flatten - return shadow - } - if shadow == nil { - shadow = make(map[string]any) - } - - var m2 map[string]any - if prefix != "" { - prefix += delimiter - } - for k, val := range m { - fullKey := prefix + k - switch val := val.(type) { - case map[string]any: - m2 = val - case map[any]any: - m2 = cast.ToStringMap(val) - default: - // immediate value - shadow[strings.ToLower(fullKey)] = val - continue - } - // recursively merge to shadow map - shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) - } - return shadow -} diff --git a/viper.go b/viper.go index 9fc4fd325..6ec598c48 100644 --- a/viper.go +++ b/viper.go @@ -40,7 +40,6 @@ import ( "github.com/spf13/cast" "github.com/spf13/pflag" - "github.com/spf13/viper/internal/encoding/ini" "github.com/spf13/viper/internal/features" ) @@ -163,9 +162,6 @@ type Viper struct { configPermissions os.FileMode envPrefix string - // Specific commands for ini parsing - iniLoadOptions ini.LoadOptions - automaticEnvApplied bool envKeyReplacer StringReplacer allowEmptyEnv bool @@ -1937,13 +1933,6 @@ func (v *Viper) SetConfigPermissions(perm os.FileMode) { v.configPermissions = perm.Perm() } -// IniLoadOptions sets the load options for ini parsing. -func IniLoadOptions(in ini.LoadOptions) Option { - return optionFunc(func(v *Viper) { - v.iniLoadOptions = in - }) -} - func (v *Viper) getConfigType() string { if v.configType != "" { return v.configType