Skip to content

Commit

Permalink
feat: complete support encode map or struct data to properties text
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 5, 2022
1 parent bd35eb9 commit 0fb7060
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 29 deletions.
7 changes: 7 additions & 0 deletions any.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build !go1.18
// +build !go1.18

package properties

// alias of interface{}, use for go < 1.18
type any = interface{}
63 changes: 40 additions & 23 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package properties

import (
"bytes"
"errors"
"reflect"
"strings"

"github.com/gookit/goutil/strutil"
"github.com/gookit/goutil/reflects"
"github.com/mitchellh/mapstructure"
)

// Encoder struct
type Encoder struct {
buf bytes.Buffer
// TagName for encode a struct. default: properties
TagName string
}
Expand All @@ -21,49 +25,62 @@ func NewEncoder() *Encoder {
}

// Marshal data(struct, map) to properties text
func (e *Encoder) Marshal(v interface{}) ([]byte, error) {
func (e *Encoder) Marshal(v any) ([]byte, error) {
return e.Encode(v)
}

// Encode data(struct, map) to properties text
func (e *Encoder) Encode(v interface{}) ([]byte, error) {
mp, ok := v.(map[string]interface{})
func (e *Encoder) Encode(v any) ([]byte, error) {
if v == nil {
return nil, nil
}

if err := e.encode(v); err != nil {
return nil, err
}
return e.buf.Bytes(), nil
}

// try convert v to map[string]interface{}
if !ok {
mp = make(map[string]interface{})
// Encode data(struct, map) to properties text
func (e *Encoder) encode(v any) error {
rv := reflect.Indirect(reflect.ValueOf(v))

// convert struct to map[string]any
if rv.Kind() == reflect.Struct {
mp := make(map[string]any)
cfg := &mapstructure.DecoderConfig{
TagName: e.TagName,
Result: &mp,
}

decoder, err := mapstructure.NewDecoder(cfg)
if err != nil {
return nil, err
return err
}

if err := decoder.Decode(v); err != nil {
return nil, err
return err
}

rv = reflect.ValueOf(mp)
} else if rv.Kind() != reflect.Map {
return errors.New("only allow encode map and struct data")
}

return e.encode(mp)
// TODO collect to map[string]string then sort keys
reflects.FlatMap(rv, e.writeln)
return nil
}

func (e *Encoder) encode(mp map[string]interface{}) ([]byte, error) {
var path string
var buf bytes.Buffer

// TODO sort keys
func (e *Encoder) writeln(path string, rv reflect.Value) {
e.buf.WriteString(path)
e.buf.WriteByte('=')

// TODO...
for name, val := range mp {
path = name
buf.WriteString(path)
buf.WriteByte('=')
buf.WriteString(strutil.QuietString(val))
buf.WriteByte('\n')
val := reflects.String(rv)
if rv.Kind() == reflect.String && strings.ContainsRune(val, '\n') {
val = strings.Replace(val, "\n", "\\\n", -1)
}

return buf.Bytes(), nil
e.buf.WriteString(val)
e.buf.WriteByte('\n')
}
19 changes: 17 additions & 2 deletions encoder_test.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
package properties_test

import (
"fmt"
"testing"

"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/testutil/assert"
"github.com/gookit/properties"
)

func TestEncode(t *testing.T) {
e := properties.NewEncoder()
bs, err := e.Marshal(maputil.Data{
bs, err := e.Marshal(map[string]any{
"name": "inhere",
"age": 234,
"str1": "a string",
"str2": "a multi \nline string",
"top": map[string]any{
"sub0": "val0",
"sub1": []string{"val1-0", "val1-1"},
},
})

str := string(bs)
fmt.Println(str)
assert.NoErr(t, err)
assert.NotEmpty(t, bs)
assert.StrContains(t, str, "name=inhere")
assert.StrContains(t, str, "top.sub1[0]=val1-0")
assert.StrContains(t, str, "str2=a multi \\\nline string")

bs, err = properties.Marshal(nil)
assert.NoErr(t, err)
assert.Nil(t, bs)
}
8 changes: 4 additions & 4 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (p *Parser) WithOptions(optFns ...OpFunc) *Parser {
}

// Unmarshal parse properties text and decode to struct
func (p *Parser) Unmarshal(v []byte, ptr interface{}) error {
func (p *Parser) Unmarshal(v []byte, ptr any) error {
if err := p.ParseBytes(v); err != nil {
return err
}
Expand Down Expand Up @@ -281,7 +281,7 @@ func (p *Parser) setValue(key, value, comments string) {
}
}

var setVal interface{}
var setVal any
setVal = value
p.smap[key] = value

Expand Down Expand Up @@ -348,8 +348,8 @@ func (p *Parser) setValueByItem(ti tokenItem) {
var ErrNotFound = errors.New("this key does not exists")

// MapStruct mapping data to a struct ptr
func (p *Parser) MapStruct(key string, ptr interface{}) error {
var data interface{}
func (p *Parser) MapStruct(key string, ptr any) error {
var data any
if key == "" { // binding all data
data = p.Data
} else { // sub data of the p.Data
Expand Down

0 comments on commit 0fb7060

Please sign in to comment.