diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..0e6a00b8 --- /dev/null +++ b/config/config.go @@ -0,0 +1,43 @@ +package config + +import ( + "fmt" +) + +type ConfigContainer interface { + Set(key, val string) error + String(key string) string + Int(key string) (int, error) + Int64(key string) (int64, error) + Bool(key string) (bool, error) + Float(key string) (float64, error) + DIY(key string) (interface{}, error) +} + +type Config interface { + Parse(key string) (ConfigContainer, error) +} + +var adapters = make(map[string]Config) + +// Register makes a config adapter available by the adapter name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, adapter Config) { + if adapter == nil { + panic("config: Register adapter is nil") + } + if _, dup := adapters[name]; dup { + panic("config: Register called twice for adapter " + name) + } + adapters[name] = adapter +} + +// config need to be correct JSON as string: {"interval":360} +func NewConfig(adapterName, fileaname string) (ConfigContainer, error) { + adapter, ok := adapters[adapterName] + if !ok { + return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName) + } + return adapter.Parse(fileaname) +} diff --git a/config/ini.go b/config/ini.go new file mode 100644 index 00000000..1effaea1 --- /dev/null +++ b/config/ini.go @@ -0,0 +1,134 @@ +package config + +import ( + "bufio" + "bytes" + "errors" + "io" + "os" + "strconv" + "strings" + "sync" + "unicode" +) + +var ( + bComment = []byte{'#'} + bEmpty = []byte{} + bEqual = []byte{'='} + bDQuote = []byte{'"'} +) + +type IniConfig struct { +} + +// ParseFile creates a new Config and parses the file configuration from the +// named file. +func (ini *IniConfig) Parse(name string) (ConfigContainer, error) { + file, err := os.Open(name) + if err != nil { + return nil, err + } + + cfg := &IniConfigContainer{ + file.Name(), + make(map[int][]string), + make(map[string]string), + make(map[string]int64), + sync.RWMutex{}, + } + cfg.Lock() + defer cfg.Unlock() + defer file.Close() + + var comment bytes.Buffer + buf := bufio.NewReader(file) + + for nComment, off := 0, int64(1); ; { + line, _, err := buf.ReadLine() + if err == io.EOF { + break + } + if bytes.Equal(line, bEmpty) { + continue + } + + off += int64(len(line)) + + if bytes.HasPrefix(line, bComment) { + line = bytes.TrimLeft(line, "#") + line = bytes.TrimLeftFunc(line, unicode.IsSpace) + comment.Write(line) + comment.WriteByte('\n') + continue + } + if comment.Len() != 0 { + cfg.comment[nComment] = []string{comment.String()} + comment.Reset() + nComment++ + } + + val := bytes.SplitN(line, bEqual, 2) + if bytes.HasPrefix([]byte(strings.TrimSpace(string(val[1]))), bDQuote) { + val[1] = bytes.Trim([]byte(strings.TrimSpace(string(val[1]))), `"`) + } + + key := strings.TrimSpace(string(val[0])) + cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) + cfg.data[key] = strings.TrimSpace(string(val[1])) + cfg.offset[key] = off + } + return cfg, nil +} + +// A Config represents the configuration. +type IniConfigContainer struct { + filename string + comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. + data map[string]string // key: value + offset map[string]int64 // key: offset; for editing. + sync.RWMutex +} + +// Bool returns the boolean value for a given key. +func (c *IniConfigContainer) Bool(key string) (bool, error) { + return strconv.ParseBool(c.data[key]) +} + +// Int returns the integer value for a given key. +func (c *IniConfigContainer) Int(key string) (int, error) { + return strconv.Atoi(c.data[key]) +} + +func (c *IniConfigContainer) Int64(key string) (int64, error) { + return strconv.ParseInt(c.data[key], 10, 64) +} + +// Float returns the float value for a given key. +func (c *IniConfigContainer) Float(key string) (float64, error) { + return strconv.ParseFloat(c.data[key], 64) +} + +// String returns the string value for a given key. +func (c *IniConfigContainer) String(key string) string { + return c.data[key] +} + +// WriteValue writes a new value for key. +func (c *IniConfigContainer) Set(key, value string) error { + c.Lock() + defer c.Unlock() + c.data[key] = value + return nil +} + +func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) { + if v, ok := c.data[key]; ok { + return v, nil + } + return v, errors.New("key not find") +} + +func init() { + Register("ini", &IniConfig{}) +} diff --git a/config/ini_test.go b/config/ini_test.go new file mode 100644 index 00000000..444fa40f --- /dev/null +++ b/config/ini_test.go @@ -0,0 +1,66 @@ +package config + +import ( + "os" + "testing" +) + +var inicontext = ` +appname = beeapi +httpport = 8080 +mysqlport = 3600 +PI = 3.1415976 +runmode = "dev" +autorender = false +copyrequestbody = true +` + +func TestIni(t *testing.T) { + f, err := os.Create("testini.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(inicontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testini.conf") + iniconf, err := NewConfig("ini", "testini.conf") + if err != nil { + t.Fatal(err) + } + if iniconf.String("appname") != "beeapi" { + t.Fatal("appname not equal to beeapi") + } + if port, err := iniconf.Int("httpport"); err != nil || port != 8080 { + t.Error(port) + t.Fatal(err) + } + if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 { + t.Error(port) + t.Fatal(err) + } + if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 { + t.Error(pi) + t.Fatal(err) + } + if iniconf.String("runmode") != "dev" { + t.Fatal("runmode not equal to dev") + } + if v, err := iniconf.Bool("autorender"); err != nil || v != false { + t.Error(v) + t.Fatal(err) + } + if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true { + t.Error(v) + t.Fatal(err) + } + if err = iniconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + if iniconf.String("name") != "astaxie" { + t.Fatal("get name error") + } +} diff --git a/config/json.go b/config/json.go new file mode 100644 index 00000000..2b4021e8 --- /dev/null +++ b/config/json.go @@ -0,0 +1,90 @@ +package config + +import ( + "encoding/json" + "errors" + "io/ioutil" + "os" + "sync" +) + +type JsonConfig struct { +} + +func (js *JsonConfig) Parse(filename string) (ConfigContainer, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + x := &JsonConfigContainer{ + data: make(map[string]interface{}), + } + content, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + err = json.Unmarshal(content, &x.data) + if err != nil { + return nil, err + } + return x, nil +} + +type JsonConfigContainer struct { + data map[string]interface{} + sync.Mutex +} + +func (c *JsonConfigContainer) Bool(key string) (bool, error) { + if v, ok := c.data[key].(bool); ok { + return v, nil + } + return false, errors.New("not bool value") +} + +func (c *JsonConfigContainer) Int(key string) (int, error) { + if v, ok := c.data[key].(float64); ok { + return int(v), nil + } + return 0, errors.New("not int value") +} + +func (c *JsonConfigContainer) Int64(key string) (int64, error) { + if v, ok := c.data[key].(float64); ok { + return int64(v), nil + } + return 0, errors.New("not bool value") +} + +func (c *JsonConfigContainer) Float(key string) (float64, error) { + if v, ok := c.data[key].(float64); ok { + return v, nil + } + return 0.0, errors.New("not float64 value") +} + +func (c *JsonConfigContainer) String(key string) string { + if v, ok := c.data[key].(string); ok { + return v + } + return "" +} + +func (c *JsonConfigContainer) Set(key, val string) error { + c.Lock() + defer c.Unlock() + c.data[key] = val + return nil +} + +func (c *JsonConfigContainer) DIY(key string) (v interface{}, err error) { + if v, ok := c.data[key]; ok { + return v, nil + } + return nil, errors.New("not exist key") +} + +func init() { + Register("json", &JsonConfig{}) +} diff --git a/config/json_test.go b/config/json_test.go new file mode 100644 index 00000000..6e207585 --- /dev/null +++ b/config/json_test.go @@ -0,0 +1,66 @@ +package config + +import ( + "os" + "testing" +) + +var jsoncontext = `{ +"appname": "beeapi", +"httpport": 8080, +"mysqlport": 3600, +"PI": 3.1415976, +"runmode": "dev", +"autorender": false, +"copyrequestbody": true +}` + +func TestJson(t *testing.T) { + f, err := os.Create("testjson.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(jsoncontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testjson.conf") + jsonconf, err := NewConfig("json", "testjson.conf") + if err != nil { + t.Fatal(err) + } + if jsonconf.String("appname") != "beeapi" { + t.Fatal("appname not equal to beeapi") + } + if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 { + t.Error(port) + t.Fatal(err) + } + if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 { + t.Error(port) + t.Fatal(err) + } + if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 { + t.Error(pi) + t.Fatal(err) + } + if jsonconf.String("runmode") != "dev" { + t.Fatal("runmode not equal to dev") + } + if v, err := jsonconf.Bool("autorender"); err != nil || v != false { + t.Error(v) + t.Fatal(err) + } + if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true { + t.Error(v) + t.Fatal(err) + } + if err = jsonconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + if jsonconf.String("name") != "astaxie" { + t.Fatal("get name error") + } +} diff --git a/config/xml.go b/config/xml.go new file mode 100644 index 00000000..a4f9735c --- /dev/null +++ b/config/xml.go @@ -0,0 +1,82 @@ +//xml parse should incluce in tags + +package config + +import ( + "errors" + "github.com/clbanning/x2j" + "io/ioutil" + "os" + "strconv" + "sync" +) + +type XMLConfig struct { +} + +func (xmls *XMLConfig) Parse(filename string) (ConfigContainer, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + x := &XMLConfigContainer{ + data: make(map[string]interface{}), + } + content, err := ioutil.ReadAll(file) + if err != nil { + return nil, err + } + d, err := x2j.DocToMap(string(content)) + if err != nil { + return nil, err + } + x.data = d["config"].(map[string]interface{}) + return x, nil +} + +type XMLConfigContainer struct { + data map[string]interface{} + sync.Mutex +} + +func (c *XMLConfigContainer) Bool(key string) (bool, error) { + return strconv.ParseBool(c.data[key].(string)) +} + +func (c *XMLConfigContainer) Int(key string) (int, error) { + return strconv.Atoi(c.data[key].(string)) +} + +func (c *XMLConfigContainer) Int64(key string) (int64, error) { + return strconv.ParseInt(c.data[key].(string), 10, 64) +} + +func (c *XMLConfigContainer) Float(key string) (float64, error) { + return strconv.ParseFloat(c.data[key].(string), 64) +} + +func (c *XMLConfigContainer) String(key string) string { + if v, ok := c.data[key].(string); ok { + return v + } + return "" +} + +func (c *XMLConfigContainer) Set(key, val string) error { + c.Lock() + defer c.Unlock() + c.data[key] = val + return nil +} + +func (c *XMLConfigContainer) DIY(key string) (v interface{}, err error) { + if v, ok := c.data[key]; ok { + return v, nil + } + return nil, errors.New("not exist key") +} + +func init() { + Register("xml", &XMLConfig{}) +} diff --git a/config/xml_test.go b/config/xml_test.go new file mode 100644 index 00000000..1e429e06 --- /dev/null +++ b/config/xml_test.go @@ -0,0 +1,69 @@ +package config + +import ( + "os" + "testing" +) + +//xml parse should incluce in tags +var xmlcontext = ` + +beeapi +8080 +3600 +3.1415976 +dev +false +true + +` + +func TestXML(t *testing.T) { + f, err := os.Create("testxml.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(xmlcontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testxml.conf") + xmlconf, err := NewConfig("xml", "testxml.conf") + if err != nil { + t.Fatal(err) + } + if xmlconf.String("appname") != "beeapi" { + t.Fatal("appname not equal to beeapi") + } + if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 { + t.Error(port) + t.Fatal(err) + } + if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 { + t.Error(port) + t.Fatal(err) + } + if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 { + t.Error(pi) + t.Fatal(err) + } + if xmlconf.String("runmode") != "dev" { + t.Fatal("runmode not equal to dev") + } + if v, err := xmlconf.Bool("autorender"); err != nil || v != false { + t.Error(v) + t.Fatal(err) + } + if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true { + t.Error(v) + t.Fatal(err) + } + if err = xmlconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + if xmlconf.String("name") != "astaxie" { + t.Fatal("get name error") + } +} diff --git a/config/yaml.go b/config/yaml.go new file mode 100644 index 00000000..8ba3ca58 --- /dev/null +++ b/config/yaml.go @@ -0,0 +1,126 @@ +package config + +import ( + "bytes" + "encoding/json" + "errors" + "github.com/wendal/goyaml2" + "io/ioutil" + "log" + "os" + "sync" +) + +type YAMLConfig struct { +} + +func (yaml *YAMLConfig) Parse(filename string) (ConfigContainer, error) { + y := &YAMLConfigContainer{ + data: make(map[string]interface{}), + } + cnf, err := ReadYmlReader(filename) + if err != nil { + return nil, err + } + y.data = cnf + return y, nil +} + +// 从Reader读取YAML +func ReadYmlReader(path string) (cnf map[string]interface{}, err error) { + err = nil + f, err := os.Open(path) + if err != nil { + return + } + defer f.Close() + err = nil + buf, err := ioutil.ReadAll(f) + if err != nil || len(buf) < 3 { + return + } + + if string(buf[0:1]) == "{" { + log.Println("Look lile a Json, try it") + err = json.Unmarshal(buf, &cnf) + if err == nil { + log.Println("It is Json Map") + return + } + } + + _map, _err := goyaml2.Read(bytes.NewBuffer(buf)) + if _err != nil { + log.Println("Goyaml2 ERR>", string(buf), _err) + //err = goyaml.Unmarshal(buf, &cnf) + err = _err + return + } + if _map == nil { + log.Println("Goyaml2 output nil? Pls report bug\n" + string(buf)) + } + cnf, ok := _map.(map[string]interface{}) + if !ok { + log.Println("Not a Map? >> ", string(buf), _map) + cnf = nil + } + return +} + +type YAMLConfigContainer struct { + data map[string]interface{} + sync.Mutex +} + +func (c *YAMLConfigContainer) Bool(key string) (bool, error) { + if v, ok := c.data[key].(bool); ok { + return v, nil + } + return false, errors.New("not bool value") +} + +func (c *YAMLConfigContainer) Int(key string) (int, error) { + if v, ok := c.data[key].(int); ok { + return v, nil + } + return 0, errors.New("not int value") +} + +func (c *YAMLConfigContainer) Int64(key string) (int64, error) { + if v, ok := c.data[key].(int64); ok { + return v, nil + } + return 0, errors.New("not bool value") +} + +func (c *YAMLConfigContainer) Float(key string) (float64, error) { + if v, ok := c.data[key].(float64); ok { + return v, nil + } + return 0.0, errors.New("not float64 value") +} + +func (c *YAMLConfigContainer) String(key string) string { + if v, ok := c.data[key].(string); ok { + return v + } + return "" +} + +func (c *YAMLConfigContainer) Set(key, val string) error { + c.Lock() + defer c.Unlock() + c.data[key] = val + return nil +} + +func (c *YAMLConfigContainer) DIY(key string) (v interface{}, err error) { + if v, ok := c.data[key]; ok { + return v, nil + } + return nil, errors.New("not exist key") +} + +func init() { + Register("yaml", &YAMLConfig{}) +} diff --git a/config/yaml_tast.go b/config/yaml_tast.go new file mode 100644 index 00000000..45142e76 --- /dev/null +++ b/config/yaml_tast.go @@ -0,0 +1,66 @@ +package config + +import ( + "os" + "testing" +) + +var yamlcontext = ` +"appname": "beeapi", +"httpport": 8080, +"mysqlport": 3600, +"PI": 3.1415976, +"runmode": "dev", +"autorender": false, +"copyrequestbody": true +` + +func TestYaml(t *testing.T) { + f, err := os.Create("testyaml.conf") + if err != nil { + t.Fatal(err) + } + _, err = f.WriteString(yamlcontext) + if err != nil { + f.Close() + t.Fatal(err) + } + f.Close() + defer os.Remove("testyaml.conf") + yamlconf, err := NewConfig("yaml", "testyaml.conf") + if err != nil { + t.Fatal(err) + } + if yamlconf.String("appname") != "beeapi" { + t.Fatal("appname not equal to beeapi") + } + if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 { + t.Error(port) + t.Fatal(err) + } + if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 { + t.Error(port) + t.Fatal(err) + } + if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 { + t.Error(pi) + t.Fatal(err) + } + if yamlconf.String("runmode") != "dev" { + t.Fatal("runmode not equal to dev") + } + if v, err := yamlconf.Bool("autorender"); err != nil || v != false { + t.Error(v) + t.Fatal(err) + } + if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true { + t.Error(v) + t.Fatal(err) + } + if err = yamlconf.Set("name", "astaxie"); err != nil { + t.Fatal(err) + } + if yamlconf.String("name") != "astaxie" { + t.Fatal("get name error") + } +}