forked from mirror/viper
feat(encoding): integrate Java properties codec into Viper
Signed-off-by: Mark Sagi-Kazar <mark.sagikazar@gmail.com>
This commit is contained in:
parent
858ffb6bd0
commit
72453f720e
|
@ -12,10 +12,18 @@ import (
|
||||||
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
|
||||||
type Codec struct {
|
type Codec struct {
|
||||||
KeyDelimiter string
|
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) {
|
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
p := properties.NewProperties()
|
if c.Properties == nil {
|
||||||
|
c.Properties = properties.NewProperties()
|
||||||
|
}
|
||||||
|
|
||||||
flattened := map[string]interface{}{}
|
flattened := map[string]interface{}{}
|
||||||
|
|
||||||
|
@ -30,7 +38,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
|
|
||||||
for _, key := range 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -38,7 +46,7 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
_, err := p.WriteComment(&buf, "#", properties.UTF8)
|
_, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -46,15 +54,16 @@ func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
p, err := properties.Load(b, properties.UTF8)
|
var err error
|
||||||
|
c.Properties, err = properties.Load(b, properties.UTF8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, key := range p.Keys() {
|
for _, key := range c.Properties.Keys() {
|
||||||
// ignore existence check: we know it's there
|
// ignore existence check: we know it's there
|
||||||
value, _ := p.Get(key)
|
value, _ := c.Properties.Get(key)
|
||||||
|
|
||||||
// recursively build nested maps
|
// recursively build nested maps
|
||||||
path := strings.Split(key, c.keyDelimiter())
|
path := strings.Split(key, c.keyDelimiter())
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// original form of the data
|
// original form of the data
|
||||||
const original = `# key-value pair
|
const original = `#key-value pair
|
||||||
key = value
|
key = value
|
||||||
map.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
57
viper.go
57
viper.go
|
@ -35,7 +35,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/magiconair/properties"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -45,6 +44,7 @@ import (
|
||||||
"github.com/spf13/viper/internal/encoding"
|
"github.com/spf13/viper/internal/encoding"
|
||||||
"github.com/spf13/viper/internal/encoding/hcl"
|
"github.com/spf13/viper/internal/encoding/hcl"
|
||||||
"github.com/spf13/viper/internal/encoding/ini"
|
"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/json"
|
||||||
"github.com/spf13/viper/internal/encoding/toml"
|
"github.com/spf13/viper/internal/encoding/toml"
|
||||||
"github.com/spf13/viper/internal/encoding/yaml"
|
"github.com/spf13/viper/internal/encoding/yaml"
|
||||||
|
@ -215,10 +215,6 @@ type Viper struct {
|
||||||
aliases map[string]string
|
aliases map[string]string
|
||||||
typeByDefValue bool
|
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)
|
onConfigChange func(fsnotify.Event)
|
||||||
|
|
||||||
logger Logger
|
logger Logger
|
||||||
|
@ -356,6 +352,21 @@ func (v *Viper) resetEncoding() {
|
||||||
decoderRegistry.RegisterDecoder("ini", codec)
|
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.encoderRegistry = encoderRegistry
|
||||||
v.decoderRegistry = decoderRegistry
|
v.decoderRegistry = decoderRegistry
|
||||||
}
|
}
|
||||||
|
@ -1656,7 +1667,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||||
buf.ReadFrom(in)
|
buf.ReadFrom(in)
|
||||||
|
|
||||||
switch format := strings.ToLower(v.getConfigType()); format {
|
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)
|
err := v.decoderRegistry.Decode(format, buf.Bytes(), c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigParseError{err}
|
return ConfigParseError{err}
|
||||||
|
@ -1670,22 +1681,6 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error {
|
||||||
for k, v := range env {
|
for k, v := range env {
|
||||||
c[k] = v
|
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)
|
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 {
|
func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||||
c := v.AllSettings()
|
c := v.AllSettings()
|
||||||
switch configType {
|
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)
|
b, err := v.encoderRegistry.Encode(configType, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ConfigMarshalError{err}
|
return ConfigMarshalError{err}
|
||||||
|
@ -1707,22 +1702,6 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error {
|
||||||
return ConfigMarshalError{err}
|
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":
|
case "dotenv", "env":
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
for _, key := range v.AllKeys() {
|
for _, key := range v.AllKeys() {
|
||||||
|
|
Loading…
Reference in New Issue