Merge pull request #1806 from happyinsect/master

add support for .properties configuration file
This commit is contained in:
John Guo
2022-05-13 20:01:29 +08:00
committed by GitHub
11 changed files with 489 additions and 9 deletions

View File

@ -22,13 +22,14 @@ import (
)
const (
ContentTypeJson = `json`
ContentTypeJs = `js`
ContentTypeXml = `xml`
ContentTypeIni = `ini`
ContentTypeYaml = `yaml`
ContentTypeYml = `yml`
ContentTypeToml = `toml`
ContentTypeJson = `json`
ContentTypeJs = `js`
ContentTypeXml = `xml`
ContentTypeIni = `ini`
ContentTypeYaml = `yaml`
ContentTypeYml = `yml`
ContentTypeToml = `toml`
ContentTypeProperties = `properties`
)
const (

View File

@ -8,6 +8,7 @@ package gjson
import (
"github.com/gogf/gf/v2/encoding/gini"
"github.com/gogf/gf/v2/encoding/gproperties"
"github.com/gogf/gf/v2/encoding/gtoml"
"github.com/gogf/gf/v2/encoding/gxml"
"github.com/gogf/gf/v2/encoding/gyaml"
@ -197,3 +198,30 @@ func (j *Json) MustToIni() []byte {
func (j *Json) MustToIniString() string {
return string(j.MustToIni())
}
// ========================================================================
// properties
// ========================================================================
// Toproperties json to properties
func (j *Json) ToProperties() ([]byte, error) {
return gproperties.Encode(j.Map())
}
// TopropertiesString properties to string
func (j *Json) ToPropertiesString() (string, error) {
b, e := j.ToProperties()
return string(b), e
}
func (j *Json) MustToProperties() []byte {
result, err := j.ToProperties()
if err != nil {
panic(err)
}
return result
}
// MustTopropertiesString
func (j *Json) MustToPropertiesString() string {
return string(j.MustToProperties())
}

View File

@ -11,6 +11,7 @@ import (
"reflect"
"github.com/gogf/gf/v2/encoding/gini"
"github.com/gogf/gf/v2/encoding/gproperties"
"github.com/gogf/gf/v2/encoding/gtoml"
"github.com/gogf/gf/v2/encoding/gxml"
"github.com/gogf/gf/v2/encoding/gyaml"
@ -174,6 +175,17 @@ func LoadToml(data interface{}, safe ...bool) (*Json, error) {
return doLoadContentWithOptions(gconv.Bytes(data), option)
}
// LoadProperties creates a Json object from given TOML format content.
func LoadProperties(data interface{}, safe ...bool) (*Json, error) {
option := Options{
Type: ContentTypeProperties,
}
if len(safe) > 0 && safe[0] {
option.Safe = true
}
return doLoadContentWithOptions(gconv.Bytes(data), option)
}
// LoadContent creates a Json object from given content, it checks the data type of `content`
// automatically, supporting data content type as follows:
// JSON, XML, INI, YAML and TOML.
@ -222,7 +234,8 @@ func IsValidDataType(dataType string) bool {
ContentTypeYaml,
ContentTypeYml,
ContentTypeToml,
ContentTypeIni:
ContentTypeIni,
ContentTypeProperties:
return true
}
return false
@ -288,6 +301,10 @@ func doLoadContentWithOptions(data []byte, options Options) (*Json, error) {
if data, err = gini.ToJson(data); err != nil {
return nil, err
}
case ContentTypeProperties:
if data, err = gproperties.ToJson(data); err != nil {
return nil, err
}
default:
err = gerror.NewCodef(
@ -335,6 +352,8 @@ func checkDataType(content []byte) string {
(gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*".+"`, content) || gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content)) {
// Must contain "[xxx]" section.
return ContentTypeIni
} else if gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*\w+`, content) {
return ContentTypeProperties
} else {
return ""
}

View File

@ -182,6 +182,7 @@ func ExampleIsValidDataType() {
fmt.Println(gjson.IsValidDataType("txt"))
fmt.Println(gjson.IsValidDataType(""))
fmt.Println(gjson.IsValidDataType(".json"))
fmt.Println(gjson.IsValidDataType(".properties"))
// Output:
// true
@ -192,6 +193,7 @@ func ExampleIsValidDataType() {
// false
// false
// true
// true
}
func ExampleLoad_Xml() {
@ -200,3 +202,16 @@ func ExampleLoad_Xml() {
fmt.Println(j.Get("doc.name"))
fmt.Println(j.Get("doc.score"))
}
func ExampleLoad_Properties() {
jsonFilePath := gtest.DataPath("properties", "data1.properties")
j, _ := gjson.Load(jsonFilePath)
fmt.Println(j.Get("pr.name"))
fmt.Println(j.Get("pr.score"))
fmt.Println(j.Get("pr.sex"))
//Output:
// john
// 100
// 0
}

View File

@ -641,6 +641,80 @@ func ExampleJson_MustToIniString() {
//Name=John
}
// ========================================================================
// Properties
// ========================================================================
func ExampleJson_ToProperties() {
type BaseInfo struct {
Name string
Age int
}
info := BaseInfo{
Name: "John",
Age: 18,
}
j := gjson.New(info)
pr, _ := j.ToProperties()
fmt.Println(string(pr))
// May Output:
// name = John
// age = 18
}
func ExampleJson_ToPropertiesString() {
type BaseInfo struct {
Name string
}
info := BaseInfo{
Name: "John",
}
j := gjson.New(info)
pr, _ := j.ToPropertiesString()
fmt.Println(pr)
// Output:
// name = John
}
func ExampleJson_MustToProperties() {
type BaseInfo struct {
Name string
}
info := BaseInfo{
Name: "John",
}
j := gjson.New(info)
pr := j.MustToProperties()
fmt.Println(string(pr))
// Output:
// name = John
}
func ExampleJson_MustToPropertiesString() {
type BaseInfo struct {
Name string
}
info := BaseInfo{
Name: "John",
}
j := gjson.New(info)
pr := j.MustToPropertiesString()
fmt.Println(pr)
// Output:
// name = John
}
func ExampleJson_MarshalJSON() {
type BaseInfo struct {
Name string

View File

@ -361,3 +361,58 @@ gfcli:
t.AssertNil(err)
})
}
func Test_Load_Properties(t *testing.T) {
var data = `
#注释
addr.ip = 127.0.0.1
addr.port=9001
addr.enable=true
DBINFO.type=mysql
DBINFO.user=root
DBINFO.password=password
`
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadContent(data)
if err != nil {
gtest.Fatal(err)
}
t.Assert(j.Get("addr.ip").String(), "127.0.0.1")
t.Assert(j.Get("addr.port").String(), "9001")
t.Assert(j.Get("addr.enable").String(), "true")
t.Assert(j.Get("DBINFO.type").String(), "mysql")
t.Assert(j.Get("DBINFO.user").String(), "root")
t.Assert(j.Get("DBINFO.password").String(), "password")
_, err = j.ToProperties()
if err != nil {
gtest.Fatal(err)
}
})
gtest.C(t, func(t *gtest.T) {
j, err := gjson.LoadProperties(data, true)
if err != nil {
gtest.Fatal(err)
}
t.Assert(j.Get("addr.ip").String(), "127.0.0.1")
t.Assert(j.Get("addr.port").String(), "9001")
t.Assert(j.Get("addr.enable").String(), "true")
t.Assert(j.Get("DBINFO.type").String(), "mysql")
t.Assert(j.Get("DBINFO.user").String(), "root")
t.Assert(j.Get("DBINFO.password").String(), "password")
})
gtest.C(t, func(t *gtest.T) {
errData := []byte("i\\u1 : 123456789")
_, err := gjson.LoadContentType("properties", errData, true)
t.AssertNE(err, nil)
})
}

View File

@ -0,0 +1,3 @@
pr.name=john
pr.score=100
pr.sex=0

View File

@ -0,0 +1,137 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package gproperties provides accessing and converting for .properties content.
package gproperties
import (
"bytes"
"sort"
"strings"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/json"
"github.com/gogf/gf/v2/util/gconv"
"github.com/magiconair/properties"
)
// Decode converts properties format to map.
func Decode(data []byte) (res map[string]interface{}, err error) {
res = make(map[string]interface{})
pr, err := properties.Load(data, properties.UTF8)
if err != nil || pr == nil {
err = gerror.Wrapf(err, `Lib magiconair load Properties data failed.`)
return nil, err
}
for _, key := range pr.Keys() {
// ignore existence check: we know it's there
value, _ := pr.Get(key)
// recursively build nested maps
path := strings.Split(key, ".")
lastKey := strings.ToLower(path[len(path)-1])
deepestMap := deepSearch(res, path[0:len(path)-1])
// set innermost value
deepestMap[lastKey] = value
}
return res, nil
}
// Encode converts map to properties format.
func Encode(data map[string]interface{}) (res []byte, err error) {
pr := properties.NewProperties()
flattened := map[string]interface{}{}
flattened = flattenAndMergeMap(flattened, data, "", ".")
keys := make([]string, 0, len(flattened))
for key := range flattened {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
_, _, err := pr.Set(key, gconv.String(flattened[key]))
if err != nil {
err = gerror.Wrapf(err, `Sets the property key to the corresponding value failed.`)
return nil, err
}
}
var buf bytes.Buffer
_, err = pr.Write(&buf, properties.UTF8)
if err != nil {
err = gerror.Wrapf(err, `Properties Write buf failed.`)
return nil, err
}
return buf.Bytes(), nil
}
// ToJson convert .properties format to JSON.
func ToJson(data []byte) (res []byte, err error) {
prMap, err := Decode(data)
if err != nil {
return nil, err
}
return json.Marshal(prMap)
}
// 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.
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
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]interface{})
m[k] = m3
m = m3
continue
}
m3, ok := m2.(map[string]interface{})
if !ok {
m3 = make(map[string]interface{})
m[k] = m3
}
// continue search from here
m = m3
}
return m
}
// flattenAndMergeMap recursively flattens the given map into a new map
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
if shadow != nil && prefix != "" && shadow[prefix] != nil {
return shadow
}
var m2 map[string]interface{}
if prefix != "" {
prefix += delimiter
}
for k, val := range m {
fullKey := prefix + k
switch val.(type) {
case map[string]interface{}:
m2 = val.(map[string]interface{})
case map[interface{}]interface{}:
m2 = gconv.Map(val)
default:
// immediate value
shadow[strings.ToLower(fullKey)] = val
continue
}
// recursively merge to shadow map
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
}
return shadow
}

View File

@ -0,0 +1,132 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gproperties_test
import (
"fmt"
"strings"
"testing"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/encoding/gproperties"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/test/gtest"
)
var pStr string = `
# 模板引擎目录
viewpath = "/home/www/templates/"
# redis数据库配置
redis.disk = "127.0.0.1:6379,0"
redis.cache = "127.0.0.1:6379,1"
#SQL配置
sql.mysql.0.type = mysql
sql.mysql.0.ip = 127.0.0.1
sql.mysql.0.user = root
`
var errorTests = []struct {
input, msg string
}{
// unicode literals
{"key\\u1 = value", "invalid unicode literal"},
{"key\\u12 = value", "invalid unicode literal"},
{"key\\u123 = value", "invalid unicode literal"},
{"key\\u123g = value", "invalid unicode literal"},
{"key\\u123", "invalid unicode literal"},
// circular references
{"key=${key}", `circular reference in:\nkey=\$\{key\}`},
{"key1=${key2}\nkey2=${key1}", `circular reference in:\n(key1=\$\{key2\}\nkey2=\$\{key1\}|key2=\$\{key1\}\nkey1=\$\{key2\})`},
// malformed expressions
{"key=${ke", "malformed expression"},
{"key=valu${ke", "malformed expression"},
}
func TestDecode(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := make(map[string]interface{})
m["properties"] = pStr
res, err := gproperties.Encode(m)
if err != nil {
t.Errorf("encode failed. %v", err)
return
}
decodeMap, err := gproperties.Decode(res)
if err != nil {
t.Errorf("decode failed. %v", err)
return
}
t.Assert(decodeMap["properties"], pStr)
})
gtest.C(t, func(t *gtest.T) {
for _, v := range errorTests {
_, err := gproperties.Decode(([]byte)(v.input))
if err == nil {
t.Errorf("encode should be failed. %v", err)
return
}
t.AssertIN(`Lib magiconair load Properties data failed.`, strings.Split(err.Error(), ":"))
}
})
}
func TestEncode(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
m := make(map[string]interface{})
m["properties"] = pStr
res, err := gproperties.Encode(m)
if err != nil {
t.Errorf("encode failed. %v", err)
return
}
decodeMap, err := gproperties.Decode(res)
if err != nil {
t.Errorf("decode failed. %v", err)
return
}
t.Assert(decodeMap["properties"], pStr)
})
}
func TestToJson(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
res, err := gproperties.Encode(map[string]interface{}{
"sql": g.Map{
"userName": "admin",
"password": "123456",
},
"user": "admin",
"no": 123,
})
fmt.Print(string(res))
jsonPr, err := gproperties.ToJson(res)
if err != nil {
t.Errorf("ToJson failed. %v", err)
return
}
fmt.Print(string(jsonPr))
p := gjson.New(res)
expectJson, err := p.ToJson()
if err != nil {
t.Errorf("parser ToJson failed. %v", err)
return
}
t.Assert(jsonPr, expectJson)
})
gtest.C(t, func(t *gtest.T) {
for _, v := range errorTests {
_, err := gproperties.ToJson(([]byte)(v.input))
if err == nil {
t.Errorf("encode should be failed. %v", err)
return
}
t.AssertIN(`Lib magiconair load Properties data failed.`, strings.Split(err.Error(), ":"))
}
})
}

2
go.mod
View File

@ -10,6 +10,8 @@ require (
github.com/go-redis/redis/v8 v8.11.4
github.com/gorilla/websocket v1.5.0
github.com/grokify/html-strip-tags-go v0.0.1
github.com/kr/pretty v0.3.0 // indirect
github.com/magiconair/properties v1.8.6
github.com/olekukonko/tablewriter v0.0.5
go.opentelemetry.io/otel v1.7.0
go.opentelemetry.io/otel/sdk v1.7.0

16
go.sum
View File

@ -4,6 +4,7 @@ github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cb
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -45,6 +46,15 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
@ -67,6 +77,8 @@ github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
@ -139,8 +151,10 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=