go-common/vendor/github.com/Terry-Mao/goconf/conf.go
2019-04-22 18:49:16 +08:00

782 lines
19 KiB
Go

package goconf
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"reflect"
"strconv"
"strings"
"time"
)
const (
// formatter
CRLF = '\n'
Comment = "#"
Spliter = " "
SectionS = "["
SectionE = "]"
// memory unit
Byte = 1
KB = 1024 * Byte
MB = 1024 * KB
GB = 1024 * MB
)
// Section is the key-value data object.
type Section struct {
data map[string]string // key:value
dataOrder []string
dataComments map[string][]string // key:comments
Name string
comments []string
Comment string
}
// Config is the key-value configuration object.
type Config struct {
data map[string]*Section
dataOrder []string
file string
Comment string
Spliter string
}
// New return a new default Config object (Comment = '#', spliter = ' ').
func New() *Config {
return &Config{Comment: Comment, Spliter: Spliter, data: map[string]*Section{}}
}
// ParseReader parse config file by a io.Reader.
func (c *Config) ParseReader(reader io.Reader) error {
var (
err error
line int
idx int
row string
key string
value string
comments []string
section *Section
rd = bufio.NewReader(reader)
)
for {
line++
row, err = rd.ReadString(CRLF)
if err == io.EOF && len(row) == 0 {
// file end
break
} else if err != nil && err != io.EOF {
return err
}
row = strings.TrimSpace(row)
// ignore blank line
// ignore Comment line
if len(row) == 0 || strings.HasPrefix(row, c.Comment) {
comments = append(comments, row)
continue
}
// get secion
if strings.HasPrefix(row, SectionS) {
if !strings.HasSuffix(row, SectionE) {
return errors.New(fmt.Sprintf("no end section: %s at :%d", SectionE, line))
}
sectionStr := row[1 : len(row)-1]
// store the section
s, ok := c.data[sectionStr]
if !ok {
s = &Section{data: map[string]string{}, dataComments: map[string][]string{}, comments: comments, Comment: c.Comment, Name: sectionStr}
c.data[sectionStr] = s
c.dataOrder = append(c.dataOrder, sectionStr)
} else {
return errors.New(fmt.Sprintf("section: %s already exists at %d", sectionStr, line))
}
section = s
comments = []string{}
continue
}
// get the spliter index
idx = strings.Index(row, c.Spliter)
if idx > 0 {
// get the key and value
key = strings.TrimSpace(row[:idx])
if len(row) > idx {
value = strings.TrimSpace(row[idx+1:])
}
} else {
return errors.New(fmt.Sprintf("no spliter in key: %s at %d", row, line))
}
// check section exists
if section == nil {
return errors.New(fmt.Sprintf("no section for key: %s at %d", key, line))
}
// check key already exists
if _, ok := section.data[key]; ok {
return errors.New(fmt.Sprintf("section: %s already has key: %s at %d", section.Name, key, line))
}
// save key-value
section.data[key] = value
// save comments for key
section.dataComments[key] = comments
section.dataOrder = append(section.dataOrder, key)
// clean comments
comments = []string{}
}
return nil
}
// Parse parse the specified config file.
func (c *Config) Parse(file string) error {
// open config file
if f, err := os.Open(file); err != nil {
return err
} else {
defer f.Close()
c.file = file
return c.ParseReader(f)
}
}
// Get get a config section by key.
func (c *Config) Get(section string) *Section {
s, _ := c.data[section]
return s
}
// Add add a new config section, if exist the section key then return the existing one.
func (c *Config) Add(section string, comments ...string) *Section {
s, ok := c.data[section]
if !ok {
var dataComments []string
for _, comment := range comments {
for _, line := range strings.Split(comment, string(CRLF)) {
dataComments = append(dataComments, fmt.Sprintf("%s%s", c.Comment, line))
}
}
s = &Section{data: map[string]string{}, Name: section, comments: dataComments, Comment: c.Comment, dataComments: map[string][]string{}}
c.data[section] = s
c.dataOrder = append(c.dataOrder, section)
}
return s
}
// Remove remove the specified section.
func (c *Config) Remove(section string) {
if _, ok := c.data[section]; ok {
for i, k := range c.dataOrder {
if k == section {
c.dataOrder = append(c.dataOrder[:i], c.dataOrder[i+1:]...)
break
}
}
delete(c.data, section)
}
}
// Sections return all the config sections.
func (c *Config) Sections() []string {
// safe-copy
sections := []string{}
for _, k := range c.dataOrder {
sections = append(sections, k)
}
return sections
}
// Save save current configuration to specified file, if file is "" then rewrite the original file.
func (c *Config) Save(file string) error {
if file == "" {
file = c.file
} else {
c.file = file
}
// save core file
return c.saveFile(file)
}
// saveFile save config info in specified file.
func (c *Config) saveFile(file string) error {
f, err := os.OpenFile(file, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
return err
}
defer f.Close()
// sections
for _, section := range c.dataOrder {
data, _ := c.data[section]
// comments
for _, comment := range data.comments {
if _, err := f.WriteString(fmt.Sprintf("%s%c", comment, CRLF)); err != nil {
return err
}
}
// section
if _, err := f.WriteString(fmt.Sprintf("[%s]%c", section, CRLF)); err != nil {
return err
}
// key-values
for _, k := range data.dataOrder {
v, _ := data.data[k]
// comments
for _, comment := range data.dataComments[k] {
if _, err := f.WriteString(fmt.Sprintf("%s%c", comment, CRLF)); err != nil {
return err
}
}
// key-value
if _, err := f.WriteString(fmt.Sprintf("%s%s%s%c", k, c.Spliter, v, CRLF)); err != nil {
return err
}
}
}
return nil
}
// Reload reload the config file and return a new Config.
func (c *Config) Reload() (*Config, error) {
nc := &Config{Comment: c.Comment, Spliter: c.Spliter, file: c.file, data: map[string]*Section{}}
if err := nc.Parse(c.file); err != nil {
return nil, err
}
return nc, nil
}
// Add add a new key-value configuration for the section.
func (s *Section) Add(k, v string, comments ...string) {
if _, ok := s.data[k]; !ok {
s.dataOrder = append(s.dataOrder, k)
for _, comment := range comments {
for _, line := range strings.Split(comment, string(CRLF)) {
s.dataComments[k] = append(s.dataComments[k], fmt.Sprintf("%s%s", s.Comment, line))
}
}
}
s.data[k] = v
}
// Remove remove the specified key configuration for the section.
func (s *Section) Remove(k string) {
delete(s.data, k)
for i, key := range s.dataOrder {
if key == k {
s.dataOrder = append(s.dataOrder[:i], s.dataOrder[i+1:]...)
break
}
}
}
// An NoKeyError describes a goconf key that was not found in the section.
type NoKeyError struct {
Key string
Section string
}
func (e *NoKeyError) Error() string {
return fmt.Sprintf("key: \"%s\" not found in [%s]", e.Key, e.Section)
}
// String get config string value.
func (s *Section) String(key string) (string, error) {
if v, ok := s.data[key]; ok {
return v, nil
} else {
return "", &NoKeyError{Key: key, Section: s.Name}
}
}
// Strings get config []string value.
func (s *Section) Strings(key, delim string) ([]string, error) {
if v, ok := s.data[key]; ok {
return strings.Split(v, delim), nil
} else {
return nil, &NoKeyError{Key: key, Section: s.Name}
}
}
// Int get config int value.
func (s *Section) Int(key string) (int64, error) {
if v, ok := s.data[key]; ok {
return strconv.ParseInt(v, 10, 64)
} else {
return 0, &NoKeyError{Key: key, Section: s.Name}
}
}
// Uint get config uint value.
func (s *Section) Uint(key string) (uint64, error) {
if v, ok := s.data[key]; ok {
return strconv.ParseUint(v, 10, 64)
} else {
return 0, &NoKeyError{Key: key, Section: s.Name}
}
}
// Float get config float value.
func (s *Section) Float(key string) (float64, error) {
if v, ok := s.data[key]; ok {
return strconv.ParseFloat(v, 64)
} else {
return 0, &NoKeyError{Key: key, Section: s.Name}
}
}
// Bool get config boolean value.
//
// "yes", "1", "y", "true", "enable" means true.
//
// "no", "0", "n", "false", "disable" means false.
//
// if the specified value unknown then return false.
func (s *Section) Bool(key string) (bool, error) {
if v, ok := s.data[key]; ok {
v = strings.ToLower(v)
return parseBool(v), nil
} else {
return false, &NoKeyError{Key: key, Section: s.Name}
}
}
func parseBool(v string) bool {
if v == "true" || v == "yes" || v == "1" || v == "y" || v == "enable" {
return true
} else if v == "false" || v == "no" || v == "0" || v == "n" || v == "disable" {
return false
} else {
return false
}
}
// Byte get config byte number value.
//
// 1kb = 1k = 1024.
//
// 1mb = 1m = 1024 * 1024.
//
// 1gb = 1g = 1024 * 1024 * 1024.
func (s *Section) MemSize(key string) (int, error) {
if v, ok := s.data[key]; ok {
return parseMemory(v)
} else {
return 0, &NoKeyError{Key: key, Section: s.Name}
}
}
func parseMemory(v string) (int, error) {
unit := Byte
subIdx := len(v)
if strings.HasSuffix(v, "k") {
unit = KB
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "kb") {
unit = KB
subIdx = subIdx - 2
} else if strings.HasSuffix(v, "m") {
unit = MB
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "mb") {
unit = MB
subIdx = subIdx - 2
} else if strings.HasSuffix(v, "g") {
unit = GB
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "gb") {
unit = GB
subIdx = subIdx - 2
}
b, err := strconv.ParseInt(v[:subIdx], 10, 64)
if err != nil {
return 0, err
}
return int(b) * unit, nil
}
// Duration get config second value.
//
// 1s = 1sec = 1.
//
// 1m = 1min = 60.
//
// 1h = 1hour = 60 * 60.
func (s *Section) Duration(key string) (time.Duration, error) {
if v, ok := s.data[key]; ok {
if t, err := parseTime(v); err != nil {
return 0, err
} else {
return time.Duration(t), nil
}
} else {
return 0, &NoKeyError{Key: key, Section: s.Name}
}
}
func parseTime(v string) (int64, error) {
unit := int64(time.Nanosecond)
subIdx := len(v)
if strings.HasSuffix(v, "ms") {
unit = int64(time.Millisecond)
subIdx = subIdx - 2
} else if strings.HasSuffix(v, "s") {
unit = int64(time.Second)
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "sec") {
unit = int64(time.Second)
subIdx = subIdx - 3
} else if strings.HasSuffix(v, "m") {
unit = int64(time.Minute)
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "min") {
unit = int64(time.Minute)
subIdx = subIdx - 3
} else if strings.HasSuffix(v, "h") {
unit = int64(time.Hour)
subIdx = subIdx - 1
} else if strings.HasSuffix(v, "hour") {
unit = int64(time.Hour)
subIdx = subIdx - 4
}
b, err := strconv.ParseInt(v[:subIdx], 10, 64)
if err != nil {
return 0, err
}
return b * unit, nil
}
// Keys return all the section keys.
func (s *Section) Keys() []string {
keys := []string{}
for k, _ := range s.data {
keys = append(keys, k)
}
return keys
}
// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal.
// (The argument to Unmarshal must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "goconf: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Ptr {
return "goconf: Unmarshal(non-pointer " + e.Type.String() + ")"
}
return "goconf: Unmarshal(nil " + e.Type.String() + ")"
}
// Unmarshal parses the goconf struct and stores the result in the value
// pointed to by v.
//
// Struct values encode as goconf objects. Each exported struct field
// becomes a member of the object unless
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option.
// The empty values are false, 0, any
// nil pointer or interface value, and any array, slice, map, or string of
// length zero. The object's section and key string is the struct field name
// but can be specified in the struct field's tag value. The "goconf" key in
// the struct field's tag value is the key name, followed by an optional comma
// and options. Examples:
//
// // Field is ignored by this package.
// Field int `goconf:"-"`
//
// // Field appears in goconf section "base" as key "myName".
// Field int `goconf:"base:myName"`
//
// // Field appears in goconf section "base" as key "myName", the value split
// // by delimiter ",".
// Field []string `goconf:"base:myName:,"`
//
// // Field appears in goconf section "base" as key "myName", the value split
// // by delimiter "," and key-value is splited by "=".
// Field map[int]string `goconf:"base:myName:,"`
//
// // Field appears in goconf section "base" as key "myName", the value
// // conver to time.Duration. When has extra tag "time", then goconf can
// // parse such "1h", "1s" config values.
// //
// // Note the extra tag "time" only effect the int64 (time.Duration is int64)
// Field time.Duration `goconf:"base:myName:time"`
//
// // Field appears in goconf section "base" as key "myName", when has extra
// // tag, then goconf can parse like "1gb", "1mb" config values.
// //
// // Note the extra tag "memory" only effect the int (memory size is int).
// Field int `goconf:"base:myName:memory"`
//
func (c *Config) Unmarshal(v interface{}) error {
vv := reflect.ValueOf(v)
if vv.Kind() != reflect.Ptr || vv.IsNil() {
return &InvalidUnmarshalError{reflect.TypeOf(v)}
}
rv := vv.Elem()
rt := rv.Type()
n := rv.NumField()
// enum every struct field
for i := 0; i < n; i++ {
vf := rv.Field(i)
tf := rt.Field(i)
tag := tf.Tag.Get("goconf")
// if tag empty or "-" ignore
if tag == "-" || tag == "" || tag == "omitempty" {
continue
}
tagArr := strings.SplitN(tag, ":", 3)
if len(tagArr) < 2 {
return errors.New(fmt.Sprintf("error tag: %s, must be section:field:delim(optional)", tag))
}
section := tagArr[0]
key := tagArr[1]
s := c.Get(section)
if s == nil {
// no config section
continue
}
value, ok := s.data[key]
if !ok {
// no confit key
continue
}
switch vf.Kind() {
case reflect.String:
vf.SetString(value)
case reflect.Bool:
vf.SetBool(parseBool(value))
case reflect.Float32:
if tmp, err := strconv.ParseFloat(value, 32); err != nil {
return err
} else {
vf.SetFloat(tmp)
}
case reflect.Float64:
if tmp, err := strconv.ParseFloat(value, 64); err != nil {
return err
} else {
vf.SetFloat(tmp)
}
case reflect.Int:
if len(tagArr) == 3 {
format := tagArr[2]
// parse memory size
if format == "memory" {
if tmp, err := parseMemory(value); err != nil {
return err
} else {
vf.SetInt(int64(tmp))
}
} else {
return errors.New(fmt.Sprintf("unknown tag: %s in struct field: %s (support tags: \"memory\")", format, tf.Name))
}
} else {
if tmp, err := strconv.ParseInt(value, 10, 32); err != nil {
return err
} else {
vf.SetInt(tmp)
}
}
case reflect.Int8:
if tmp, err := strconv.ParseInt(value, 10, 8); err != nil {
return err
} else {
vf.SetInt(tmp)
}
case reflect.Int16:
if tmp, err := strconv.ParseInt(value, 10, 16); err != nil {
return err
} else {
vf.SetInt(tmp)
}
case reflect.Int32:
if tmp, err := strconv.ParseInt(value, 10, 32); err != nil {
return err
} else {
vf.SetInt(tmp)
}
case reflect.Int64:
if len(tagArr) == 3 {
format := tagArr[2]
// parse time
if format == "time" {
if tmp, err := parseTime(value); err != nil {
return err
} else {
vf.SetInt(tmp)
}
} else {
return errors.New(fmt.Sprintf("unknown tag: %s in struct field: %s (support tags: \"time\")", format, tf.Name))
}
} else {
if tmp, err := strconv.ParseInt(value, 10, 64); err != nil {
return err
} else {
vf.SetInt(tmp)
}
}
case reflect.Uint:
if tmp, err := strconv.ParseUint(value, 10, 32); err != nil {
return err
} else {
vf.SetUint(tmp)
}
case reflect.Uint8:
if tmp, err := strconv.ParseUint(value, 10, 8); err != nil {
return err
} else {
vf.SetUint(tmp)
}
case reflect.Uint16:
if tmp, err := strconv.ParseUint(value, 10, 16); err != nil {
return err
} else {
vf.SetUint(tmp)
}
case reflect.Uint32:
if tmp, err := strconv.ParseUint(value, 10, 32); err != nil {
return err
} else {
vf.SetUint(tmp)
}
case reflect.Uint64:
if tmp, err := strconv.ParseUint(value, 10, 64); err != nil {
return err
} else {
vf.SetUint(tmp)
}
case reflect.Slice:
delim := ","
if len(tagArr) > 2 {
delim = tagArr[2]
}
strs := strings.Split(value, delim)
sli := reflect.MakeSlice(tf.Type, 0, len(strs))
for _, str := range strs {
vv, err := getValue(tf.Type.Elem().String(), str)
if err != nil {
return err
}
sli = reflect.Append(sli, vv)
}
vf.Set(sli)
case reflect.Map:
delim := ","
if len(tagArr) > 2 {
delim = tagArr[2]
}
strs := strings.Split(value, delim)
m := reflect.MakeMap(tf.Type)
for _, str := range strs {
mapStrs := strings.SplitN(str, "=", 2)
if len(mapStrs) < 2 {
return errors.New(fmt.Sprintf("error map: %s, must be split by \"=\"", str))
}
vk, err := getValue(tf.Type.Key().String(), mapStrs[0])
if err != nil {
return err
}
vv, err := getValue(tf.Type.Elem().String(), mapStrs[1])
if err != nil {
return err
}
m.SetMapIndex(vk, vv)
}
vf.Set(m)
default:
return errors.New(fmt.Sprintf("cannot unmarshall unsuported kind: %s into struct field: %s", vf.Kind().String(), tf.Name))
}
}
return nil
}
// getValue parse String to the type "t" reflect.Value.
func getValue(t, v string) (reflect.Value, error) {
var vv reflect.Value
switch t {
case "bool":
d := parseBool(v)
vv = reflect.ValueOf(d)
case "int":
d, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(int(d))
case "int8":
d, err := strconv.ParseInt(v, 10, 8)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(int8(d))
case "int16":
d, err := strconv.ParseInt(v, 10, 16)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(int16(d))
case "int32":
d, err := strconv.ParseInt(v, 10, 32)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(int32(d))
case "int64":
d, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(int64(d))
case "uint":
d, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(uint(d))
case "uint8":
d, err := strconv.ParseUint(v, 10, 8)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(uint8(d))
case "uint16":
d, err := strconv.ParseUint(v, 10, 16)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(uint16(d))
case "uint32":
d, err := strconv.ParseUint(v, 10, 32)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(uint32(d))
case "uint64":
d, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(uint64(d))
case "float32":
d, err := strconv.ParseFloat(v, 32)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(float32(d))
case "float64":
d, err := strconv.ParseFloat(v, 64)
if err != nil {
return vv, err
}
vv = reflect.ValueOf(float64(d))
case "string":
vv = reflect.ValueOf(v)
default:
return vv, errors.New(fmt.Sprintf("unkown type: %s", t))
}
return vv, nil
}