add config module

This commit is contained in:
siddontang 2014-07-22 16:42:16 +08:00
parent f9bb9e3d84
commit cde2e20315
4 changed files with 342 additions and 0 deletions

163
config/config.go Normal file
View File

@ -0,0 +1,163 @@
/*
Package config is a simple tool to handle key-value config, like Redis's configuration:
# commet
key = value
*/
package config
import (
"errors"
"fmt"
"strconv"
"strings"
)
var (
ErrNil = errors.New("nil value")
)
type Config struct {
Values map[string]string
}
func NewConfig() *Config {
c := &Config{}
c.Values = make(map[string]string)
return c
}
/*
bool: true, false, 0, 1, or ""
*/
func (c *Config) GetBool(key string) (bool, error) {
v, err := c.GetString(key)
if err != nil {
return false, err
}
v = strings.ToLower(v)
switch v {
case "true", "1":
return true, nil
case "false", "0", "":
return false, nil
default:
return false, fmt.Errorf("invalid bool format %s", v)
}
}
/*
int may be pure number or below format:
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
*/
func (c *Config) GetInt64(key string) (int64, error) {
v, err := c.GetString(key)
if err != nil {
return 0, err
}
if len(v) == 0 {
return 0, ErrNil
}
var scale int64 = 1
v = strings.ToLower(v)
var b bool = false
if v[len(v)-1] == 'b' {
v = v[0 : len(v)-1]
b = true
}
if len(v) == 0 {
return 0, fmt.Errorf("invalid number format %s", v)
}
switch v[len(v)-1] {
case 'k':
v = v[0 : len(v)-1]
if b {
scale = 1024
} else {
scale = 1000
}
break
case 'm':
v = v[0 : len(v)-1]
if b {
scale = 1024 * 1024
} else {
scale = 1000 * 1000
}
case 'g':
v = v[0 : len(v)-1]
if b {
scale = 1024 * 1024 * 1024
} else {
scale = 1000 * 1000 * 1000
}
}
var n int64
n, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, err
}
return n * scale, nil
}
func (c *Config) GetUint64(key string) (uint64, error) {
v, err := c.GetInt64(key)
if v < 0 {
return 0, fmt.Errorf("negative number %d", v)
}
return uint64(v), err
}
func (c *Config) GetInt(key string) (int, error) {
v, err := c.GetInt64(key)
return int(v), err
}
func (c *Config) GetString(key string) (string, error) {
v, ok := c.Values[key]
if !ok {
return "", ErrNil
} else {
return v, nil
}
}
func (c *Config) SetString(key string, value string) {
c.Values[key] = value
}
func (c *Config) SetInt64(key string, n int64) {
c.Values[key] = strconv.FormatInt(n, 10)
}
func (c *Config) SetUint64(key string, n uint64) {
c.Values[key] = strconv.FormatUint(n, 10)
}
func (c *Config) SetInt(key string, n int) {
c.SetInt64(key, int64(n))
}
func (c *Config) SetBool(key string, v bool) {
if v {
c.Values[key] = "true"
} else {
c.Values[key] = "false"
}
}

79
config/config_test.go Normal file
View File

@ -0,0 +1,79 @@
package config
import (
"bytes"
"fmt"
"testing"
)
func testConfig(cfg *Config, t *testing.T) {
if v, err := cfg.GetBool("a"); err != nil {
t.Fatal(err)
} else if v != true {
t.Fatal(v)
}
checkInt := func(t *testing.T, cfg *Config, key string, check int) {
if v, err := cfg.GetInt(key); err != nil {
t.Fatal(err)
} else if v != check {
t.Fatal(fmt.Sprintf("%s %d != %d", key, v, check))
}
}
checkInt(t, cfg, "b", 100)
checkInt(t, cfg, "kb", 1024)
checkInt(t, cfg, "k", 1000)
checkInt(t, cfg, "mb", 1024*1024)
checkInt(t, cfg, "m", 1000*1000)
checkInt(t, cfg, "gb", 1024*1024*1024)
checkInt(t, cfg, "g", 1000*1000*1000)
}
func TestGetConfig(t *testing.T) {
cfg := NewConfig()
cfg.Values["a"] = "true"
cfg.Values["b"] = "100"
cfg.Values["kb"] = "1kb"
cfg.Values["k"] = "1k"
cfg.Values["mb"] = "1mb"
cfg.Values["m"] = "1m"
cfg.Values["gb"] = "1gb"
cfg.Values["g"] = "1g"
testConfig(cfg, t)
}
func TestReadWriteConfig(t *testing.T) {
var b = []byte(`
# comment
a = true
b = 100
kb = 1kb
k = 1k
mb = 1mb
m = 1m
gb = 1gb
g = 1g
`)
cfg, err := ReadConfig(b)
if err != nil {
t.Fatal(err)
}
testConfig(cfg, t)
var buf bytes.Buffer
if err := cfg.Write(&buf); err != nil {
t.Fatal(err)
}
cfg.Values = make(map[string]string)
if err := cfg.Read(&buf); err != nil {
t.Fatal(err)
}
testConfig(cfg, t)
}

63
config/read.go Normal file
View File

@ -0,0 +1,63 @@
package config
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"
)
func ReadConfigFile(name string) (*Config, error) {
data, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
return ReadConfig(data)
}
func ReadConfig(data []byte) (*Config, error) {
cfg := NewConfig()
if err := cfg.Read(bytes.NewBuffer(data)); err != nil {
return nil, err
}
return cfg, nil
}
func (c *Config) Read(r io.Reader) error {
rb := bufio.NewReaderSize(r, 4096)
for {
l, err := rb.ReadString('\n')
if err != nil && err != io.EOF {
return err
} else if err == io.EOF {
break
}
l = strings.TrimSpace(l)
if len(l) == 0 {
continue
}
//comment
if l[0] == '#' {
continue
}
ps := strings.Split(l, "=")
if len(ps) > 2 {
return fmt.Errorf("invalid line format %s", l)
} else if len(ps) == 1 {
c.SetString(ps[0], "")
} else {
c.SetString(strings.TrimSpace(ps[0]), strings.TrimSpace(ps[1]))
}
}
return nil
}

37
config/write.go Normal file
View File

@ -0,0 +1,37 @@
package config
import (
"bytes"
"fmt"
"io"
"os"
)
func (c *Config) Write(w io.Writer) error {
var buf bytes.Buffer
for k, v := range c.Values {
buf.WriteString(fmt.Sprintf("%s = %s\n", k, v))
}
_, err := w.Write(buf.Bytes())
return err
}
func (c *Config) WriteFile(filePath string) error {
filePathBak := fmt.Sprintf("%s.bak.tmp", filePath)
fd, err := os.OpenFile(filePathBak, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
err = c.Write(fd)
fd.Close()
if err != nil {
return err
}
return os.Rename(filePathBak, filePath)
}