diff --git a/encoding/gini/gini.go b/encoding/gini/gini.go new file mode 100644 index 000000000..35740c453 --- /dev/null +++ b/encoding/gini/gini.go @@ -0,0 +1,113 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gini provides accessing and converting for INI content. +package gini + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "strings" +) + +//Decode converts INI format to map +func Decode(data []byte) (res map[string]interface{}, err error) { + res = make(map[string]interface{}) + fieldMap := make(map[string]interface{}) + + a := bytes.NewReader(data) + + r := bufio.NewReader(a) + + var section string + var lastSection string + var haveSection bool + for { + line, err := r.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + + lineStr := strings.TrimSpace(string(line)) + if len(lineStr) == 0 { + continue + } + + if lineStr[0] == ';' || lineStr[0] == '#' { + continue + } + + sectionBeginPos := strings.Index(lineStr, "[") + sectionEndPos := strings.Index(lineStr, "]") + + if sectionBeginPos >= 0 && sectionEndPos >= 2 { + section = lineStr[sectionBeginPos+1 : sectionEndPos] + + if lastSection == "" { + lastSection = section + } else if lastSection != section { + lastSection = section + fieldMap = make(map[string]interface{}) + } + haveSection = true + } else if haveSection == false { + continue + } + + if strings.Contains(lineStr, "=") && haveSection { + values := strings.Split(lineStr, "=") + + fieldMap[strings.TrimSpace(values[0])] = strings.TrimSpace(strings.Join(values[1:], "")) + res[section] = fieldMap + } + + } + + return res, nil +} + +//Encode converts map to INI format +func Encode(data map[string]interface{}) (res []byte, err error) { + w := new(bytes.Buffer) + + w.WriteString(";gini\n") + for k, v := range data { + n, err := w.WriteString(fmt.Sprintf("[%s]\n", k)) + if err != nil || n == 0 { + return nil, fmt.Errorf("write data failed. %v", err) + } + + for kk, vv := range v.(map[string]interface{}) { + n, err := w.WriteString(fmt.Sprintf("%s=%s\n", kk, vv.(string))) + if err != nil || n == 0 { + return nil, fmt.Errorf("write data failed. %v", err) + } + } + } + res = make([]byte, w.Len()) + n, err := w.Read(res) + if err != nil || n == 0 { + return nil, fmt.Errorf("write data failed. %v", err) + } + + return res, nil +} + +//ToJson convert INI format to JSON +func ToJson(data []byte) (res []byte, err error) { + iniMap, err := Decode(data) + if err != nil { + return nil, err + } + + return json.Marshal(iniMap) +} diff --git a/encoding/gini/gini_test.go b/encoding/gini/gini_test.go new file mode 100644 index 000000000..da75c22a3 --- /dev/null +++ b/encoding/gini/gini_test.go @@ -0,0 +1,97 @@ +// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gini_test + +import ( + "fmt" + "github.com/gogf/gf/encoding/gini" + "github.com/gogf/gf/encoding/gjson" + "github.com/gogf/gf/test/gtest" + "testing" +) + +var iniContent = ` + +;注释 +aa=bb +[addr] +#注释 +ip = 127.0.0.1 +port=9001 +enable=true + + [DBINFO] + type=mysql + user=root + password=password +[键] +呵呵=值 + +` + +func TestDecode(t *testing.T) { + gtest.Case(t, func() { + res, err := gini.Decode([]byte(iniContent)) + if err != nil { + gtest.Fatal(err) + } + fmt.Println(res) + gtest.Assert(res["addr"].(map[string]interface{})["ip"], "127.0.0.1") + gtest.Assert(res["addr"].(map[string]interface{})["port"], "9001") + gtest.Assert(res["DBINFO"].(map[string]interface{})["user"], "root") + gtest.Assert(res["DBINFO"].(map[string]interface{})["type"], "mysql") + gtest.Assert(res["键"].(map[string]interface{})["呵呵"], "值") + }) + +} + +func TestEncode(t *testing.T) { + gtest.Case(t, func() { + iniMap, err := gini.Decode([]byte(iniContent)) + if err != nil { + gtest.Fatal(err) + } + + iniStr, err := gini.Encode(iniMap) + if err != nil { + gtest.Fatal(err) + } + + res, err := gini.Decode(iniStr) + if err != nil { + gtest.Fatal(err) + } + + gtest.Assert(res["addr"].(map[string]interface{})["ip"], "127.0.0.1") + gtest.Assert(res["addr"].(map[string]interface{})["port"], "9001") + gtest.Assert(res["DBINFO"].(map[string]interface{})["user"], "root") + gtest.Assert(res["DBINFO"].(map[string]interface{})["type"], "mysql") + + }) +} + +func TestToJson(t *testing.T) { + gtest.Case(t, func() { + jsonStr, err := gini.ToJson([]byte(iniContent)) + if err != nil { + gtest.Fatal(err) + } + + json, err := gjson.LoadContent(jsonStr) + if err != nil { + gtest.Fatal(err) + } + + iniMap, err := gini.Decode([]byte(iniContent)) + gtest.Assert(err, nil) + + gtest.Assert(iniMap["addr"].(map[string]interface{})["ip"], json.GetString("addr.ip")) + gtest.Assert(iniMap["addr"].(map[string]interface{})["port"], json.GetString("addr.port")) + gtest.Assert(iniMap["DBINFO"].(map[string]interface{})["user"], json.GetString("DBINFO.user")) + gtest.Assert(iniMap["DBINFO"].(map[string]interface{})["type"], json.GetString("DBINFO.type")) + }) +} diff --git a/encoding/gjson/gjson_api_encoding.go b/encoding/gjson/gjson_api_encoding.go index bbce2cf8f..1b49183fd 100644 --- a/encoding/gjson/gjson_api_encoding.go +++ b/encoding/gjson/gjson_api_encoding.go @@ -8,7 +8,7 @@ package gjson import ( "encoding/json" - + "github.com/gogf/gf/encoding/gini" "github.com/gogf/gf/encoding/gtoml" "github.com/gogf/gf/encoding/gxml" "github.com/gogf/gf/encoding/gyaml" @@ -75,3 +75,14 @@ func (j *Json) ToTomlString() (string, error) { b, e := j.ToToml() return string(b), e } + +func (j *Json) ToIni() ([]byte, error) { + j.mu.RLock() + defer j.mu.RUnlock() + return gini.Encode((*(j.p)).(map[string]interface{})) +} + +func (j *Json) ToIniString() (string, error) { + b, e := j.ToToml() + return string(b), e +} diff --git a/encoding/gjson/gjson_api_new_load.go b/encoding/gjson/gjson_api_new_load.go index 0746cc34d..12a95cc46 100644 --- a/encoding/gjson/gjson_api_new_load.go +++ b/encoding/gjson/gjson_api_new_load.go @@ -12,17 +12,16 @@ import ( "encoding/json" "errors" "fmt" - "reflect" - - "github.com/gogf/gf/os/gfile" - + "github.com/gogf/gf/encoding/gini" "github.com/gogf/gf/encoding/gtoml" "github.com/gogf/gf/encoding/gxml" "github.com/gogf/gf/encoding/gyaml" "github.com/gogf/gf/internal/rwmutex" "github.com/gogf/gf/os/gfcache" + "github.com/gogf/gf/os/gfile" "github.com/gogf/gf/text/gregex" "github.com/gogf/gf/util/gconv" + "reflect" ) // New creates a Json object with any variable type of , @@ -147,6 +146,10 @@ func LoadToml(data interface{}, safe ...bool) (*Json, error) { return doLoadContent("toml", gconv.Bytes(data), safe...) } +func LoadIni(data interface{}, safe ...bool) (*Json, error) { + return doLoadContent("ini", gconv.Bytes(data), safe...) +} + func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) { var err error var result interface{} @@ -173,7 +176,10 @@ func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) { if data, err = gtoml.ToJson(data); err != nil { return nil, err } - + case "ini", ".ini": + if data, err = gini.ToJson(data); err != nil { + return nil, err + } default: err = errors.New("unsupported type for loading") } @@ -211,6 +217,8 @@ func checkDataType(content []byte) string { return "xml" } else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*:\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*:\s*.+`, content) { return "yml" + } else if (gregex.IsMatch(`^[\s\t\[*\]].?*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t\[*\]]*[\w\-]+\s*=\s*.+`, content)) && gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false && gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=*\"*.+\"`, content) == false { + return "ini" } else if gregex.IsMatch(`^[\s\t]*[\w\-]+\s*=\s*.+`, content) || gregex.IsMatch(`\n[\s\t]*[\w\-]+\s*=\s*.+`, content) { return "toml" } else { diff --git a/encoding/gjson/gjson_z_unit_load_test.go b/encoding/gjson/gjson_z_unit_load_test.go index 7271da4f2..a729e5fa8 100644 --- a/encoding/gjson/gjson_z_unit_load_test.go +++ b/encoding/gjson/gjson_z_unit_load_test.go @@ -196,3 +196,41 @@ func Test_Load_Basic(t *testing.T) { }) } + +func Test_Load_Ini(t *testing.T) { + var data = ` + +;注释 + +[addr] +#注释 +ip = 127.0.0.1 +port=9001 +enable=true + + [DBINFO] + type=mysql + user=root + password=password + +` + + gtest.Case(t, func() { + json, err := gjson.LoadContent(data) + if err != nil { + gtest.Fatal(err) + } + + gtest.Assert(json.GetString("addr.ip"), "127.0.0.1") + gtest.Assert(json.GetString("addr.port"), "9001") + gtest.Assert(json.GetString("addr.enable"), "true") + gtest.Assert(json.GetString("DBINFO.type"), "mysql") + gtest.Assert(json.GetString("DBINFO.user"), "root") + gtest.Assert(json.GetString("DBINFO.password"), "password") + + _, err = json.ToIni() + if err != nil { + gtest.Fatal(err) + } + }) +} diff --git a/encoding/gparser/gparser_api_encoding.go b/encoding/gparser/gparser_api_encoding.go index 04a8f53c4..2047c16e8 100644 --- a/encoding/gparser/gparser_api_encoding.go +++ b/encoding/gparser/gparser_api_encoding.go @@ -37,3 +37,7 @@ func VarToYaml(value interface{}) ([]byte, error) { func VarToToml(value interface{}) ([]byte, error) { return New(value).ToToml() } + +func VarToIni(value interface{}) ([]byte, error) { + return New(value).ToIni() +} diff --git a/encoding/gparser/gparser_api_new_load.go b/encoding/gparser/gparser_api_new_load.go index b582c2c8a..59224e798 100644 --- a/encoding/gparser/gparser_api_new_load.go +++ b/encoding/gparser/gparser_api_new_load.go @@ -47,3 +47,7 @@ func LoadYaml(data interface{}, safe ...bool) (*Parser, error) { func LoadToml(data interface{}, safe ...bool) (*Parser, error) { return gjson.LoadToml(data, safe...) } + +func LoadIni(data interface{}, safe ...bool) (*Parser, error) { + return gjson.LoadIni(data, safe...) +}