2013-08-21 16:07:33 +00:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2013-12-24 13:57:33 +00:00
|
|
|
DEFAULT_SECTION = "default" // default section means if some ini items not in a section, make them in default section,
|
|
|
|
bNumComment = []byte{'#'} // number signal
|
|
|
|
bSemComment = []byte{';'} // semicolon signal
|
2013-11-27 15:55:26 +00:00
|
|
|
bEmpty = []byte{}
|
2013-12-24 13:57:33 +00:00
|
|
|
bEqual = []byte{'='} // equal signal
|
|
|
|
bDQuote = []byte{'"'} // quote signal
|
|
|
|
sectionStart = []byte{'['} // section start signal
|
|
|
|
sectionEnd = []byte{']'} // section end signal
|
2013-08-21 16:07:33 +00:00
|
|
|
)
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// IniConfig implements Config to parse ini file.
|
2013-08-21 16:07:33 +00:00
|
|
|
type IniConfig struct {
|
|
|
|
}
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// ParseFile creates a new Config and parses the file configuration from the named file.
|
2013-08-21 16:07:33 +00:00
|
|
|
func (ini *IniConfig) Parse(name string) (ConfigContainer, error) {
|
|
|
|
file, err := os.Open(name)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg := &IniConfigContainer{
|
|
|
|
file.Name(),
|
2013-11-27 15:55:26 +00:00
|
|
|
make(map[string]map[string]string),
|
|
|
|
make(map[string]string),
|
2013-08-21 16:07:33 +00:00
|
|
|
make(map[string]string),
|
|
|
|
sync.RWMutex{},
|
|
|
|
}
|
|
|
|
cfg.Lock()
|
|
|
|
defer cfg.Unlock()
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
var comment bytes.Buffer
|
|
|
|
buf := bufio.NewReader(file)
|
2013-11-27 15:55:26 +00:00
|
|
|
section := DEFAULT_SECTION
|
|
|
|
for {
|
2013-08-21 16:07:33 +00:00
|
|
|
line, _, err := buf.ReadLine()
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if bytes.Equal(line, bEmpty) {
|
|
|
|
continue
|
|
|
|
}
|
2013-11-27 15:55:26 +00:00
|
|
|
line = bytes.TrimSpace(line)
|
2013-08-21 16:07:33 +00:00
|
|
|
|
2013-11-28 03:56:13 +00:00
|
|
|
var bComment []byte
|
|
|
|
switch {
|
|
|
|
case bytes.HasPrefix(line, bNumComment):
|
|
|
|
bComment = bNumComment
|
|
|
|
case bytes.HasPrefix(line, bSemComment):
|
|
|
|
bComment = bSemComment
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
2013-11-28 03:56:13 +00:00
|
|
|
if bComment != nil {
|
|
|
|
line = bytes.TrimLeft(line, string(bComment))
|
2013-11-27 15:55:26 +00:00
|
|
|
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
|
|
|
comment.Write(line)
|
|
|
|
comment.WriteByte('\n')
|
|
|
|
continue
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
2013-11-28 03:56:13 +00:00
|
|
|
|
2013-11-27 15:55:26 +00:00
|
|
|
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
|
|
|
|
section = string(line[1 : len(line)-1])
|
2013-11-29 02:17:35 +00:00
|
|
|
section = strings.ToLower(section) // section name case insensitive
|
2013-11-27 15:55:26 +00:00
|
|
|
if comment.Len() > 0 {
|
|
|
|
cfg.sectionComment[section] = comment.String()
|
|
|
|
comment.Reset()
|
|
|
|
}
|
|
|
|
if _, ok := cfg.data[section]; !ok {
|
|
|
|
cfg.data[section] = make(map[string]string)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if _, ok := cfg.data[section]; !ok {
|
|
|
|
cfg.data[section] = make(map[string]string)
|
|
|
|
}
|
|
|
|
keyval := bytes.SplitN(line, bEqual, 2)
|
|
|
|
val := bytes.TrimSpace(keyval[1])
|
|
|
|
if bytes.HasPrefix(val, bDQuote) {
|
|
|
|
val = bytes.Trim(val, `"`)
|
|
|
|
}
|
|
|
|
|
2013-11-29 02:17:35 +00:00
|
|
|
key := string(bytes.TrimSpace(keyval[0])) // key name case insensitive
|
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
cfg.data[section][key] = string(val)
|
|
|
|
if comment.Len() > 0 {
|
|
|
|
cfg.keycomment[section+"."+key] = comment.String()
|
|
|
|
comment.Reset()
|
|
|
|
}
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return cfg, nil
|
|
|
|
}
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// A Config represents the ini configuration.
|
|
|
|
// When set and get value, support key as section:name type.
|
2013-08-21 16:07:33 +00:00
|
|
|
type IniConfigContainer struct {
|
2013-11-27 15:55:26 +00:00
|
|
|
filename string
|
2013-12-24 13:57:33 +00:00
|
|
|
data map[string]map[string]string // section=> key:val
|
|
|
|
sectionComment map[string]string // section : comment
|
2013-11-27 15:55:26 +00:00
|
|
|
keycomment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
2013-08-21 16:07:33 +00:00
|
|
|
sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bool returns the boolean value for a given key.
|
|
|
|
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
return strconv.ParseBool(c.getdata(key))
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Int returns the integer value for a given key.
|
|
|
|
func (c *IniConfigContainer) Int(key string) (int, error) {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
return strconv.Atoi(c.getdata(key))
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// Int64 returns the int64 value for a given key.
|
2013-08-21 16:07:33 +00:00
|
|
|
func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
return strconv.ParseInt(c.getdata(key), 10, 64)
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Float returns the float value for a given key.
|
|
|
|
func (c *IniConfigContainer) Float(key string) (float64, error) {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
return strconv.ParseFloat(c.getdata(key), 64)
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the string value for a given key.
|
|
|
|
func (c *IniConfigContainer) String(key string) string {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-11-27 15:55:26 +00:00
|
|
|
return c.getdata(key)
|
2013-08-21 16:07:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteValue writes a new value for key.
|
2013-12-24 13:57:33 +00:00
|
|
|
// if write to one section, the key need be "section::key".
|
|
|
|
// if the section is not existed, it panics.
|
2013-08-21 16:07:33 +00:00
|
|
|
func (c *IniConfigContainer) Set(key, value string) error {
|
|
|
|
c.Lock()
|
|
|
|
defer c.Unlock()
|
2013-11-27 15:55:26 +00:00
|
|
|
if len(key) == 0 {
|
|
|
|
return errors.New("key is empty")
|
|
|
|
}
|
2013-11-29 02:17:35 +00:00
|
|
|
|
2013-11-27 15:55:26 +00:00
|
|
|
var section, k string
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-12-09 15:54:35 +00:00
|
|
|
sectionkey := strings.Split(key, "::")
|
2013-11-27 15:55:26 +00:00
|
|
|
if len(sectionkey) >= 2 {
|
|
|
|
section = sectionkey[0]
|
|
|
|
k = sectionkey[1]
|
|
|
|
} else {
|
|
|
|
section = DEFAULT_SECTION
|
|
|
|
k = sectionkey[0]
|
|
|
|
}
|
|
|
|
c.data[section][k] = value
|
2013-08-21 16:07:33 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// DIY returns the raw value by a given key.
|
2013-08-21 16:07:33 +00:00
|
|
|
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-08-21 16:07:33 +00:00
|
|
|
if v, ok := c.data[key]; ok {
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
return v, errors.New("key not find")
|
|
|
|
}
|
|
|
|
|
2013-12-24 13:57:33 +00:00
|
|
|
// section.key or key
|
2013-11-27 15:55:26 +00:00
|
|
|
func (c *IniConfigContainer) getdata(key string) string {
|
|
|
|
c.RLock()
|
|
|
|
defer c.RUnlock()
|
|
|
|
if len(key) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
2013-11-29 02:17:35 +00:00
|
|
|
|
2013-11-27 15:55:26 +00:00
|
|
|
var section, k string
|
2013-11-29 02:17:35 +00:00
|
|
|
key = strings.ToLower(key)
|
2013-12-09 15:54:35 +00:00
|
|
|
sectionkey := strings.Split(key, "::")
|
2013-11-27 15:55:26 +00:00
|
|
|
if len(sectionkey) >= 2 {
|
|
|
|
section = sectionkey[0]
|
|
|
|
k = sectionkey[1]
|
|
|
|
} else {
|
|
|
|
section = DEFAULT_SECTION
|
|
|
|
k = sectionkey[0]
|
|
|
|
}
|
|
|
|
if v, ok := c.data[section]; ok {
|
|
|
|
if vv, o := v[k]; o {
|
|
|
|
return vv
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2013-08-21 16:07:33 +00:00
|
|
|
func init() {
|
|
|
|
Register("ini", &IniConfig{})
|
|
|
|
}
|