diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..75050de --- /dev/null +++ b/config/config.go @@ -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" + } +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..51744e4 --- /dev/null +++ b/config/config_test.go @@ -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) +} diff --git a/config/read.go b/config/read.go new file mode 100644 index 0000000..8c61b0c --- /dev/null +++ b/config/read.go @@ -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 +} diff --git a/config/write.go b/config/write.go new file mode 100644 index 0000000..2044439 --- /dev/null +++ b/config/write.go @@ -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) +}