87f8fb07 by astaxie

finish config module support ini/json/xml/yaml

1 parent ac8853df
1 package config
2
3 import (
4 "fmt"
5 )
6
7 type ConfigContainer interface {
8 Set(key, val string) error
9 String(key string) string
10 Int(key string) (int, error)
11 Int64(key string) (int64, error)
12 Bool(key string) (bool, error)
13 Float(key string) (float64, error)
14 DIY(key string) (interface{}, error)
15 }
16
17 type Config interface {
18 Parse(key string) (ConfigContainer, error)
19 }
20
21 var adapters = make(map[string]Config)
22
23 // Register makes a config adapter available by the adapter name.
24 // If Register is called twice with the same name or if driver is nil,
25 // it panics.
26 func Register(name string, adapter Config) {
27 if adapter == nil {
28 panic("config: Register adapter is nil")
29 }
30 if _, dup := adapters[name]; dup {
31 panic("config: Register called twice for adapter " + name)
32 }
33 adapters[name] = adapter
34 }
35
36 // config need to be correct JSON as string: {"interval":360}
37 func NewConfig(adapterName, fileaname string) (ConfigContainer, error) {
38 adapter, ok := adapters[adapterName]
39 if !ok {
40 return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
41 }
42 return adapter.Parse(fileaname)
43 }
1 package config
2
3 import (
4 "bufio"
5 "bytes"
6 "errors"
7 "io"
8 "os"
9 "strconv"
10 "strings"
11 "sync"
12 "unicode"
13 )
14
15 var (
16 bComment = []byte{'#'}
17 bEmpty = []byte{}
18 bEqual = []byte{'='}
19 bDQuote = []byte{'"'}
20 )
21
22 type IniConfig struct {
23 }
24
25 // ParseFile creates a new Config and parses the file configuration from the
26 // named file.
27 func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
28 file, err := os.Open(name)
29 if err != nil {
30 return nil, err
31 }
32
33 cfg := &IniConfigContainer{
34 file.Name(),
35 make(map[int][]string),
36 make(map[string]string),
37 make(map[string]int64),
38 sync.RWMutex{},
39 }
40 cfg.Lock()
41 defer cfg.Unlock()
42 defer file.Close()
43
44 var comment bytes.Buffer
45 buf := bufio.NewReader(file)
46
47 for nComment, off := 0, int64(1); ; {
48 line, _, err := buf.ReadLine()
49 if err == io.EOF {
50 break
51 }
52 if bytes.Equal(line, bEmpty) {
53 continue
54 }
55
56 off += int64(len(line))
57
58 if bytes.HasPrefix(line, bComment) {
59 line = bytes.TrimLeft(line, "#")
60 line = bytes.TrimLeftFunc(line, unicode.IsSpace)
61 comment.Write(line)
62 comment.WriteByte('\n')
63 continue
64 }
65 if comment.Len() != 0 {
66 cfg.comment[nComment] = []string{comment.String()}
67 comment.Reset()
68 nComment++
69 }
70
71 val := bytes.SplitN(line, bEqual, 2)
72 if bytes.HasPrefix([]byte(strings.TrimSpace(string(val[1]))), bDQuote) {
73 val[1] = bytes.Trim([]byte(strings.TrimSpace(string(val[1]))), `"`)
74 }
75
76 key := strings.TrimSpace(string(val[0]))
77 cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key)
78 cfg.data[key] = strings.TrimSpace(string(val[1]))
79 cfg.offset[key] = off
80 }
81 return cfg, nil
82 }
83
84 // A Config represents the configuration.
85 type IniConfigContainer struct {
86 filename string
87 comment map[int][]string // id: []{comment, key...}; id 1 is for main comment.
88 data map[string]string // key: value
89 offset map[string]int64 // key: offset; for editing.
90 sync.RWMutex
91 }
92
93 // Bool returns the boolean value for a given key.
94 func (c *IniConfigContainer) Bool(key string) (bool, error) {
95 return strconv.ParseBool(c.data[key])
96 }
97
98 // Int returns the integer value for a given key.
99 func (c *IniConfigContainer) Int(key string) (int, error) {
100 return strconv.Atoi(c.data[key])
101 }
102
103 func (c *IniConfigContainer) Int64(key string) (int64, error) {
104 return strconv.ParseInt(c.data[key], 10, 64)
105 }
106
107 // Float returns the float value for a given key.
108 func (c *IniConfigContainer) Float(key string) (float64, error) {
109 return strconv.ParseFloat(c.data[key], 64)
110 }
111
112 // String returns the string value for a given key.
113 func (c *IniConfigContainer) String(key string) string {
114 return c.data[key]
115 }
116
117 // WriteValue writes a new value for key.
118 func (c *IniConfigContainer) Set(key, value string) error {
119 c.Lock()
120 defer c.Unlock()
121 c.data[key] = value
122 return nil
123 }
124
125 func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
126 if v, ok := c.data[key]; ok {
127 return v, nil
128 }
129 return v, errors.New("key not find")
130 }
131
132 func init() {
133 Register("ini", &IniConfig{})
134 }
1 package config
2
3 import (
4 "os"
5 "testing"
6 )
7
8 var inicontext = `
9 appname = beeapi
10 httpport = 8080
11 mysqlport = 3600
12 PI = 3.1415976
13 runmode = "dev"
14 autorender = false
15 copyrequestbody = true
16 `
17
18 func TestIni(t *testing.T) {
19 f, err := os.Create("testini.conf")
20 if err != nil {
21 t.Fatal(err)
22 }
23 _, err = f.WriteString(inicontext)
24 if err != nil {
25 f.Close()
26 t.Fatal(err)
27 }
28 f.Close()
29 defer os.Remove("testini.conf")
30 iniconf, err := NewConfig("ini", "testini.conf")
31 if err != nil {
32 t.Fatal(err)
33 }
34 if iniconf.String("appname") != "beeapi" {
35 t.Fatal("appname not equal to beeapi")
36 }
37 if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
38 t.Error(port)
39 t.Fatal(err)
40 }
41 if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
42 t.Error(port)
43 t.Fatal(err)
44 }
45 if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
46 t.Error(pi)
47 t.Fatal(err)
48 }
49 if iniconf.String("runmode") != "dev" {
50 t.Fatal("runmode not equal to dev")
51 }
52 if v, err := iniconf.Bool("autorender"); err != nil || v != false {
53 t.Error(v)
54 t.Fatal(err)
55 }
56 if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
57 t.Error(v)
58 t.Fatal(err)
59 }
60 if err = iniconf.Set("name", "astaxie"); err != nil {
61 t.Fatal(err)
62 }
63 if iniconf.String("name") != "astaxie" {
64 t.Fatal("get name error")
65 }
66 }
1 package config
2
3 import (
4 "encoding/json"
5 "errors"
6 "io/ioutil"
7 "os"
8 "sync"
9 )
10
11 type JsonConfig struct {
12 }
13
14 func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) {
15 file, err := os.Open(filename)
16 if err != nil {
17 return nil, err
18 }
19 defer file.Close()
20 x := &JsonConfigContainer{
21 data: make(map[string]interface{}),
22 }
23 content, err := ioutil.ReadAll(file)
24 if err != nil {
25 return nil, err
26 }
27 err = json.Unmarshal(content, &x.data)
28 if err != nil {
29 return nil, err
30 }
31 return x, nil
32 }
33
34 type JsonConfigContainer struct {
35 data map[string]interface{}
36 sync.Mutex
37 }
38
39 func (c *JsonConfigContainer) Bool(key string) (bool, error) {
40 if v, ok := c.data[key].(bool); ok {
41 return v, nil
42 }
43 return false, errors.New("not bool value")
44 }
45
46 func (c *JsonConfigContainer) Int(key string) (int, error) {
47 if v, ok := c.data[key].(float64); ok {
48 return int(v), nil
49 }
50 return 0, errors.New("not int value")
51 }
52
53 func (c *JsonConfigContainer) Int64(key string) (int64, error) {
54 if v, ok := c.data[key].(float64); ok {
55 return int64(v), nil
56 }
57 return 0, errors.New("not bool value")
58 }
59
60 func (c *JsonConfigContainer) Float(key string) (float64, error) {
61 if v, ok := c.data[key].(float64); ok {
62 return v, nil
63 }
64 return 0.0, errors.New("not float64 value")
65 }
66
67 func (c *JsonConfigContainer) String(key string) string {
68 if v, ok := c.data[key].(string); ok {
69 return v
70 }
71 return ""
72 }
73
74 func (c *JsonConfigContainer) Set(key, val string) error {
75 c.Lock()
76 defer c.Unlock()
77 c.data[key] = val
78 return nil
79 }
80
81 func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) {
82 if v, ok := c.data[key]; ok {
83 return v, nil
84 }
85 return nil, errors.New("not exist key")
86 }
87
88 func init() {
89 Register("json", &JsonConfig{})
90 }
1 package config
2
3 import (
4 "os"
5 "testing"
6 )
7
8 var jsoncontext = `{
9 "appname": "beeapi",
10 "httpport": 8080,
11 "mysqlport": 3600,
12 "PI": 3.1415976,
13 "runmode": "dev",
14 "autorender": false,
15 "copyrequestbody": true
16 }`
17
18 func TestJson(t *testing.T) {
19 f, err := os.Create("testjson.conf")
20 if err != nil {
21 t.Fatal(err)
22 }
23 _, err = f.WriteString(jsoncontext)
24 if err != nil {
25 f.Close()
26 t.Fatal(err)
27 }
28 f.Close()
29 defer os.Remove("testjson.conf")
30 jsonconf, err := NewConfig("json", "testjson.conf")
31 if err != nil {
32 t.Fatal(err)
33 }
34 if jsonconf.String("appname") != "beeapi" {
35 t.Fatal("appname not equal to beeapi")
36 }
37 if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
38 t.Error(port)
39 t.Fatal(err)
40 }
41 if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
42 t.Error(port)
43 t.Fatal(err)
44 }
45 if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
46 t.Error(pi)
47 t.Fatal(err)
48 }
49 if jsonconf.String("runmode") != "dev" {
50 t.Fatal("runmode not equal to dev")
51 }
52 if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
53 t.Error(v)
54 t.Fatal(err)
55 }
56 if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
57 t.Error(v)
58 t.Fatal(err)
59 }
60 if err = jsonconf.Set("name", "astaxie"); err != nil {
61 t.Fatal(err)
62 }
63 if jsonconf.String("name") != "astaxie" {
64 t.Fatal("get name error")
65 }
66 }
1 //xml parse should incluce in <config></config> tags
2
3 package config
4
5 import (
6 "errors"
7 "github.com/clbanning/x2j"
8 "io/ioutil"
9 "os"
10 "strconv"
11 "sync"
12 )
13
14 type XMLConfig struct {
15 }
16
17 func (xmls *XMLConfig) Parse(filename string) (ConfigContainer, error) {
18 file, err := os.Open(filename)
19 if err != nil {
20 return nil, err
21 }
22 defer file.Close()
23 x := &XMLConfigContainer{
24 data: make(map[string]interface{}),
25 }
26 content, err := ioutil.ReadAll(file)
27 if err != nil {
28 return nil, err
29 }
30 d, err := x2j.DocToMap(string(content))
31 if err != nil {
32 return nil, err
33 }
34 x.data = d["config"].(map[string]interface{})
35 return x, nil
36 }
37
38 type XMLConfigContainer struct {
39 data map[string]interface{}
40 sync.Mutex
41 }
42
43 func (c *XMLConfigContainer) Bool(key string) (bool, error) {
44 return strconv.ParseBool(c.data[key].(string))
45 }
46
47 func (c *XMLConfigContainer) Int(key string) (int, error) {
48 return strconv.Atoi(c.data[key].(string))
49 }
50
51 func (c *XMLConfigContainer) Int64(key string) (int64, error) {
52 return strconv.ParseInt(c.data[key].(string), 10, 64)
53 }
54
55 func (c *XMLConfigContainer) Float(key string) (float64, error) {
56 return strconv.ParseFloat(c.data[key].(string), 64)
57 }
58
59 func (c *XMLConfigContainer) String(key string) string {
60 if v, ok := c.data[key].(string); ok {
61 return v
62 }
63 return ""
64 }
65
66 func (c *XMLConfigContainer) Set(key, val string) error {
67 c.Lock()
68 defer c.Unlock()
69 c.data[key] = val
70 return nil
71 }
72
73 func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) {
74 if v, ok := c.data[key]; ok {
75 return v, nil
76 }
77 return nil, errors.New("not exist key")
78 }
79
80 func init() {
81 Register("xml", &XMLConfig{})
82 }
1 package config
2
3 import (
4 "os"
5 "testing"
6 )
7
8 //xml parse should incluce in <config></config> tags
9 var xmlcontext = `<?xml version="1.0" encoding="UTF-8"?>
10 <config>
11 <appname>beeapi</appname>
12 <httpport>8080</httpport>
13 <mysqlport>3600</mysqlport>
14 <PI>3.1415976</PI>
15 <runmode>dev</runmode>
16 <autorender>false</autorender>
17 <copyrequestbody>true</copyrequestbody>
18 </config>
19 `
20
21 func TestXML(t *testing.T) {
22 f, err := os.Create("testxml.conf")
23 if err != nil {
24 t.Fatal(err)
25 }
26 _, err = f.WriteString(xmlcontext)
27 if err != nil {
28 f.Close()
29 t.Fatal(err)
30 }
31 f.Close()
32 defer os.Remove("testxml.conf")
33 xmlconf, err := NewConfig("xml", "testxml.conf")
34 if err != nil {
35 t.Fatal(err)
36 }
37 if xmlconf.String("appname") != "beeapi" {
38 t.Fatal("appname not equal to beeapi")
39 }
40 if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 {
41 t.Error(port)
42 t.Fatal(err)
43 }
44 if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 {
45 t.Error(port)
46 t.Fatal(err)
47 }
48 if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 {
49 t.Error(pi)
50 t.Fatal(err)
51 }
52 if xmlconf.String("runmode") != "dev" {
53 t.Fatal("runmode not equal to dev")
54 }
55 if v, err := xmlconf.Bool("autorender"); err != nil || v != false {
56 t.Error(v)
57 t.Fatal(err)
58 }
59 if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true {
60 t.Error(v)
61 t.Fatal(err)
62 }
63 if err = xmlconf.Set("name", "astaxie"); err != nil {
64 t.Fatal(err)
65 }
66 if xmlconf.String("name") != "astaxie" {
67 t.Fatal("get name error")
68 }
69 }
1 package config
2
3 import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "github.com/wendal/goyaml2"
8 "io/ioutil"
9 "log"
10 "os"
11 "sync"
12 )
13
14 type YAMLConfig struct {
15 }
16
17 func (yaml *YAMLConfig) Parse(filename string) (ConfigContainer, error) {
18 y := &YAMLConfigContainer{
19 data: make(map[string]interface{}),
20 }
21 cnf, err := ReadYmlReader(filename)
22 if err != nil {
23 return nil, err
24 }
25 y.data = cnf
26 return y, nil
27 }
28
29 // 从Reader读取YAML
30 func ReadYmlReader(path string) (cnf map[string]interface{}, err error) {
31 err = nil
32 f, err := os.Open(path)
33 if err != nil {
34 return
35 }
36 defer f.Close()
37 err = nil
38 buf, err := ioutil.ReadAll(f)
39 if err != nil || len(buf) < 3 {
40 return
41 }
42
43 if string(buf[0:1]) == "{" {
44 log.Println("Look lile a Json, try it")
45 err = json.Unmarshal(buf, &cnf)
46 if err == nil {
47 log.Println("It is Json Map")
48 return
49 }
50 }
51
52 _map, _err := goyaml2.Read(bytes.NewBuffer(buf))
53 if _err != nil {
54 log.Println("Goyaml2 ERR>", string(buf), _err)
55 //err = goyaml.Unmarshal(buf, &cnf)
56 err = _err
57 return
58 }
59 if _map == nil {
60 log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf))
61 }
62 cnf, ok := _map.(map[string]interface{})
63 if !ok {
64 log.Println("Not a Map? >> ", string(buf), _map)
65 cnf = nil
66 }
67 return
68 }
69
70 type YAMLConfigContainer struct {
71 data map[string]interface{}
72 sync.Mutex
73 }
74
75 func (c *YAMLConfigContainer) Bool(key string) (bool, error) {
76 if v, ok := c.data[key].(bool); ok {
77 return v, nil
78 }
79 return false, errors.New("not bool value")
80 }
81
82 func (c *YAMLConfigContainer) Int(key string) (int, error) {
83 if v, ok := c.data[key].(int); ok {
84 return v, nil
85 }
86 return 0, errors.New("not int value")
87 }
88
89 func (c *YAMLConfigContainer) Int64(key string) (int64, error) {
90 if v, ok := c.data[key].(int64); ok {
91 return v, nil
92 }
93 return 0, errors.New("not bool value")
94 }
95
96 func (c *YAMLConfigContainer) Float(key string) (float64, error) {
97 if v, ok := c.data[key].(float64); ok {
98 return v, nil
99 }
100 return 0.0, errors.New("not float64 value")
101 }
102
103 func (c *YAMLConfigContainer) String(key string) string {
104 if v, ok := c.data[key].(string); ok {
105 return v
106 }
107 return ""
108 }
109
110 func (c *YAMLConfigContainer) Set(key, val string) error {
111 c.Lock()
112 defer c.Unlock()
113 c.data[key] = val
114 return nil
115 }
116
117 func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) {
118 if v, ok := c.data[key]; ok {
119 return v, nil
120 }
121 return nil, errors.New("not exist key")
122 }
123
124 func init() {
125 Register("yaml", &YAMLConfig{})
126 }
1 package config
2
3 import (
4 "os"
5 "testing"
6 )
7
8 var yamlcontext = `
9 "appname": "beeapi",
10 "httpport": 8080,
11 "mysqlport": 3600,
12 "PI": 3.1415976,
13 "runmode": "dev",
14 "autorender": false,
15 "copyrequestbody": true
16 `
17
18 func TestYaml(t *testing.T) {
19 f, err := os.Create("testyaml.conf")
20 if err != nil {
21 t.Fatal(err)
22 }
23 _, err = f.WriteString(yamlcontext)
24 if err != nil {
25 f.Close()
26 t.Fatal(err)
27 }
28 f.Close()
29 defer os.Remove("testyaml.conf")
30 yamlconf, err := NewConfig("yaml", "testyaml.conf")
31 if err != nil {
32 t.Fatal(err)
33 }
34 if yamlconf.String("appname") != "beeapi" {
35 t.Fatal("appname not equal to beeapi")
36 }
37 if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 {
38 t.Error(port)
39 t.Fatal(err)
40 }
41 if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 {
42 t.Error(port)
43 t.Fatal(err)
44 }
45 if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 {
46 t.Error(pi)
47 t.Fatal(err)
48 }
49 if yamlconf.String("runmode") != "dev" {
50 t.Fatal("runmode not equal to dev")
51 }
52 if v, err := yamlconf.Bool("autorender"); err != nil || v != false {
53 t.Error(v)
54 t.Fatal(err)
55 }
56 if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true {
57 t.Error(v)
58 t.Fatal(err)
59 }
60 if err = yamlconf.Set("name", "astaxie"); err != nil {
61 t.Fatal(err)
62 }
63 if yamlconf.String("name") != "astaxie" {
64 t.Fatal("get name error")
65 }
66 }
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!