Skip to content

Commit

Permalink
feat(encoding): integrate Java properties codec into Viper
Browse files Browse the repository at this point in the history
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
  • Loading branch information
sagikazarmark committed Dec 15, 2021
1 parent 858ffb6 commit 72453f7
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 48 deletions.
25 changes: 17 additions & 8 deletions internal/encoding/javaproperties/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{}

Expand All @@ -30,31 +38,32 @@ 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
}
}

var buf bytes.Buffer

_, err := p.WriteComment(&buf, "#", properties.UTF8)
_, 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]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())
Expand Down
22 changes: 21 additions & 1 deletion internal/encoding/javaproperties/codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
`
Expand Down Expand Up @@ -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)
}
}
57 changes: 18 additions & 39 deletions viper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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}
Expand All @@ -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)
Expand All @@ -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}
Expand All @@ -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() {
Expand Down

0 comments on commit 72453f7

Please sign in to comment.