From 72453f720eec36d722d8b1b3368fac007a36b7a7 Mon Sep 17 00:00:00 2001 From: Mark Sagi-Kazar Date: Fri, 16 Jul 2021 03:51:02 +0200 Subject: [PATCH] feat(encoding): integrate Java properties codec into Viper Signed-off-by: Mark Sagi-Kazar --- internal/encoding/javaproperties/codec.go | 25 +++++--- .../encoding/javaproperties/codec_test.go | 22 ++++++- viper.go | 57 ++++++------------- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/internal/encoding/javaproperties/codec.go b/internal/encoding/javaproperties/codec.go index ac3b66b..b8a2251 100644 --- a/internal/encoding/javaproperties/codec.go +++ b/internal/encoding/javaproperties/codec.go @@ -12,10 +12,18 @@ import ( // 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]interface{}) ([]byte, error) { - p := properties.NewProperties() +func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) { + if c.Properties == nil { + c.Properties = properties.NewProperties() + } flattened := map[string]interface{}{} @@ -30,7 +38,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) { sort.Strings(keys) for _, key := range keys { - _, _, err := p.Set(key, cast.ToString(flattened[key])) + _, _, err := c.Properties.Set(key, cast.ToString(flattened[key])) if err != nil { return nil, err } @@ -38,7 +46,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) { var buf bytes.Buffer - _, err := p.WriteComment(&buf, "#", properties.UTF8) + _, err := c.Properties.WriteComment(&buf, "#", properties.UTF8) if err != nil { return nil, err } @@ -46,15 +54,16 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) { return buf.Bytes(), nil } -func (c Codec) Decode(b []byte, v map[string]interface{}) error { - p, err := properties.Load(b, properties.UTF8) +func (c *Codec) Decode(b []byte, v map[string]interface{}) error { + var err error + c.Properties, err = properties.Load(b, properties.UTF8) if err != nil { return err } - for _, key := range p.Keys() { + for _, key := range c.Properties.Keys() { // ignore existence check: we know it's there - value, _ := p.Get(key) + value, _ := c.Properties.Get(key) // recursively build nested maps path := strings.Split(key, c.keyDelimiter()) diff --git a/internal/encoding/javaproperties/codec_test.go b/internal/encoding/javaproperties/codec_test.go index 8009707..0a33ebf 100644 --- a/internal/encoding/javaproperties/codec_test.go +++ b/internal/encoding/javaproperties/codec_test.go @@ -6,7 +6,7 @@ import ( ) // original form of the data -const original = `# key-value pair +const original = `#key-value pair key = value map.key = value ` @@ -67,3 +67,23 @@ func TestCodec_Decode(t *testing.T) { } }) } + +func TestCodec_DecodeEncode(t *testing.T) { + codec := Codec{} + + v := map[string]interface{}{} + + err := codec.Decode([]byte(original), v) + if err != nil { + t.Fatal(err) + } + + b, err := codec.Encode(data) + if err != nil { + t.Fatal(err) + } + + if original != string(b) { + t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original) + } +} diff --git a/viper.go b/viper.go index eb848f3..d78e2fc 100644 --- a/viper.go +++ b/viper.go @@ -35,7 +35,6 @@ import ( "time" "github.com/fsnotify/fsnotify" - "github.com/magiconair/properties" "github.com/mitchellh/mapstructure" "github.com/spf13/afero" "github.com/spf13/cast" @@ -45,6 +44,7 @@ import ( "github.com/spf13/viper/internal/encoding" "github.com/spf13/viper/internal/encoding/hcl" "github.com/spf13/viper/internal/encoding/ini" + "github.com/spf13/viper/internal/encoding/javaproperties" "github.com/spf13/viper/internal/encoding/json" "github.com/spf13/viper/internal/encoding/toml" "github.com/spf13/viper/internal/encoding/yaml" @@ -215,10 +215,6 @@ type Viper struct { aliases map[string]string typeByDefValue bool - // 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. - properties *properties.Properties - onConfigChange func(fsnotify.Event) logger Logger @@ -356,6 +352,21 @@ func (v *Viper) resetEncoding() { decoderRegistry.RegisterDecoder("ini", codec) } + { + codec := &javaproperties.Codec{ + KeyDelimiter: v.keyDelim, + } + + encoderRegistry.RegisterEncoder("properties", codec) + decoderRegistry.RegisterDecoder("properties", codec) + + encoderRegistry.RegisterEncoder("props", codec) + decoderRegistry.RegisterDecoder("props", codec) + + encoderRegistry.RegisterEncoder("prop", codec) + decoderRegistry.RegisterDecoder("prop", codec) + } + v.encoderRegistry = encoderRegistry v.decoderRegistry = decoderRegistry } @@ -1656,7 +1667,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { buf.ReadFrom(in) switch format := strings.ToLower(v.getConfigType()); format { - case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini": + case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop": err := v.decoderRegistry.Decode(format, buf.Bytes(), c) if err != nil { return ConfigParseError{err} @@ -1670,22 +1681,6 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { for k, v := range env { c[k] = v } - - case "properties", "props", "prop": - v.properties = properties.NewProperties() - var err error - if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { - return ConfigParseError{err} - } - for _, key := range v.properties.Keys() { - value, _ := v.properties.Get(key) - // recursively build nested maps - path := strings.Split(key, ".") - lastKey := strings.ToLower(path[len(path)-1]) - deepestMap := deepSearch(c, path[0:len(path)-1]) - // set innermost value - deepestMap[lastKey] = value - } } insensitiviseMap(c) @@ -1696,7 +1691,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { func (v *Viper) marshalWriter(f afero.File, configType string) error { c := v.AllSettings() switch configType { - case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini": + case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties": b, err := v.encoderRegistry.Encode(configType, c) if err != nil { return ConfigMarshalError{err} @@ -1707,22 +1702,6 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { return ConfigMarshalError{err} } - case "prop", "props", "properties": - if v.properties == nil { - v.properties = properties.NewProperties() - } - p := v.properties - for _, key := range v.AllKeys() { - _, _, err := p.Set(key, v.GetString(key)) - if err != nil { - return ConfigMarshalError{err} - } - } - _, err := p.WriteComment(f, "#", properties.UTF8) - if err != nil { - return ConfigMarshalError{err} - } - case "dotenv", "env": lines := []string{} for _, key := range v.AllKeys() {