配置文件的使用由来已久,从.ini、XML、JSON、YAML再到TOML,语言的表达能力越来越强,同时书写便捷性也在不断提升。 TOML是前GitHub CEO, Tom Preston-Werner,于2013年创建的语言,其目标是成为一个小规模的易于使用的语义化配置文件格式。TOML被设计为可以无二义性的转换为一个哈希表(Hash table)。
解析TOML格式的第三包使用方法,请使用度娘或谷哥搜索。这方面的资料还是相当丰富的。
比如: https://github.com/BurntSushi/toml
安装方法: go get github.com/BurntSushi/toml
很多时候我们只是把TOML格式用在配置文件中。配置文件并不复杂,可能总共就那么十几行数据。为了解析这十几行数据亲自下手解析TOML格式可就有点纠结了;直接引用第三方包吧似乎又有点轻率。那么接下来的这款轮子正适合你。总共200多行代码纯手工打造原味解析TOML格式。
package toml
import (
"bufio"
"errors"
//"fmt"
"io"
//"os"
"regexp"
"strconv"
"strings"
"time"
)
//ValueType --
type ValueType int
//List --
//Object --
const (
TABLE ValueType = iota
LEAF
KNOT ///
)
//Tree --
type Tree struct {
Value interface{}
Vtype ValueType
}
func (t *Tree) object() Object {
if t.Vtype == KNOT || t.Vtype == LEAF {
return t.Value.(Object)
} else if t.Vtype == TABLE {
tb := t.Value.(Table)
return tb[len(tb)-1]
}
return nil
}
func (t *Tree) table() Table {
if t.Vtype == TABLE {
return t.Value.(Table)
}
return nil
}
//Object --
type Object map[string]interface{}
//GetString --
func (o Object) GetString(key string) string {
if cc, ok := o[key]; ok {
if str, ok := cc.(string); ok {
return str
}
}
return ""
}
//GetFloat --
func (o Object) GetFloat(key string) (float64, error) {
str := o.GetString(key)
if len(str) > 0 {
return strconv.ParseFloat(str, 32)
}
return 0, nil
}
func (o Object) GetDateTime(key string, timeLayout string) (time.Time, error) {
str := o.GetString(key)
if len(str) > 0 {
return time.Parse(timeLayout, str)
}
return time.Time{}, errors.New("bad value for field")
}
//GetOject --
func (o Object) GetOject(path ...string) (child Object) {
child = o
for _, p := range path {
if cc, ok := child[p]; ok {
if nd, ok := cc.(*Tree); ok {
child = nd.object()
} else {
return nil
}
} else {
return nil
}
}
return
}
//GetTable --
func (o Object) GetTable(path ...string) (child Table) {
if len(path) == 0 {
return nil
}
p := o
for i, s := range path {
if cc, ok := p[s]; ok {
if nd, ok := cc.(*Tree); ok {
if len(path)-1 == i {
return nd.table()
} else {
p = nd.object()
}
} else {
return nil
}
} else {
return nil
}
}
return
}
//GetStringArray --
func (o Object) GetStringArray(key string) []string {
str := o.GetString(key)
if len(str) > 0 && str[0] == '[' && str[len(str)-1] == ']' {
list := strings.Split(str[1:len(str)-2], ",")
for i, str := range list {
list[i] = strings.Trim(str, "\" ")
}
return list
}
return nil
}
//GetFloatArray --
func (o Object) GetFloatArray(key string) []float64 {
v := o.GetString(key)
if len(v) > 0 && v[0] == '[' && v[len(v)-1] == ']' {
strs := strings.Split(v[1:len(v)-2], ",")
list := []float64{}
for _, str := range strs {
str = strings.TrimSpace(str)
if len(str) > 0 {
if f, err := strconv.ParseInt(str, 10, 64); err == nil {
list = append(list, float64(f))
} else if f, err := strconv.ParseFloat(str, 32); err == nil {
list = append(list, f)
}
}
}
return list
}
return nil
}
//Table --
type Table []Object
//NewTOMLTree --
func NewTOMLTree(rd io.Reader) (root Object) {
root = make(Object)
current := root
var s = bufio.NewScanner(rd)
s.Split(bufio.ScanLines)
for s.Scan() {
line := strings.TrimSpace(s.Text())
if len(line) > 0 && line[0] != '#' {
//[Leaf]
reg := regexp.MustCompile(`^\[\s*(\S+)\s*\]$`)
group := reg.FindStringSubmatch(line)
if len(group) == 2 {
p := root
gi := strings.Split(group[1], ".")
for i, g := range gi {
pv := p
if v, ok := pv[g]; !ok {
tmp := &Tree{make(Object), KNOT}
if len(gi)-1 == i {
tmp.Vtype = LEAF
}
pv[g] = tmp
p = tmp.object()
} else {
if len(gi)-1 == i {
if me, ok := v.(*Tree); ok {
if me.Vtype == KNOT {
me.Vtype = LEAF
} else if me.Vtype == LEAF {
me.Vtype = TABLE
me.Value = Table{me.Value.(Object), make(Object)}
} else if me.Vtype == TABLE {
me.Value = append(me.Value.(Table), make(Object))
}
} else {
panic("节点名存在冲突")
}
}
p = v.(*Tree).object()
}
current = p
}
continue
}
//Key = Value
reg = regexp.MustCompile(`^(.+)=(.*)$`)
kv := reg.FindStringSubmatch(line)
if len(kv) == 3 {
//key
key := strings.TrimSpace(kv[1])
value := strings.Trim(strings.TrimSpace(strings.Split(kv[2], "#")[0]), "\"")
obj := current
obj[key] = value
}
}
}
return
}
首先需要说明的是这款实现存在着大量不足
*因为解析方式是按行解析,所以不支持多行数组数据。形如:
hosts = [
“alpha”,
“omega”
]
*由于解析数据值并未按照TOML的语法来实现,因此不支持各种复杂或非主流写法的数据值。形如:
data = [ [“gamma”, “delta”], [1, 2] ]
*如果出现无法解析或解析不正确的数据值,看官可以亲手实现数据值的解析函数。毕竟除了我的电脑不能开源以外,解析实现的代码都在上面了。
以下是测试代码,同时也是如何使用的例子代码
//
func main() {
file, _ := os.Open("d:/toml")
//接受实现io.Reader接口的参数
tree := NewTOMLTree(file)
//最基础获取键值对
fmt.Println(tree.GetString("title"))
//单层节点下的获取键值对
owner := tree.GetOject("owner")
fmt.Println(owner.GetString("bio"))
//多层节点下的获取键值对
one:=tree.GetOject("owner","user","one")
fmt.Println(one.GetString("name"))
//键值为对象的获取方式
one = owner.GetOject("user", "one")
fmt.Println(one.GetString("duration"))
//键值为表格数据的获取方式,通过索引号指定各行数据
db := tree.GetTable("database")
//获取数组数据
fmt.Println(db[2].GetStringArray("ports"))
dc := db[0].GetOject("alpha")
fmt.Println(dc.GetString("ip"))
dc = db[2].GetOject("alpha")
fmt.Println(dc.GetString("ip"))
}
output:
TOML Example
GitHub Cofounder & CEO\nLikes tater tots and beer.
Thunder Road
4m49s
[6001 6001 6002]
10.0.0.1
11.1.1.1
用到的测试数据如下:
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[owner.user.one]
name = "Thunder Road"
duration = "4m49s"
[database.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[database]
server = "192.168.1.2"
ports = [ 7001, 7001, 7002 ]
connection_max = 5000
enabled = true
[database]
server = "192.168.1.3"
ports = [ 6001, 6001, 6002 ]
connection_max = 5000
enabled = true
[database.alpha]
ip = "11.1.1.1"
dc = "eqdc11"