229 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Beego (http://beego.me/)
 | |
| 
 | |
| // @description beego is an open-source, high-performance web framework for the Go programming language.
 | |
| 
 | |
| // @link        http://github.com/astaxie/beego for the canonical source repository
 | |
| 
 | |
| // @license     http://github.com/astaxie/beego/blob/master/LICENSE
 | |
| 
 | |
| // @authors     astaxie
 | |
| 
 | |
| package config
 | |
| 
 | |
| import (
 | |
| 	"bufio"
 | |
| 	"bytes"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"os"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"unicode"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	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
 | |
| 	bEmpty          = []byte{}
 | |
| 	bEqual          = []byte{'='} // equal signal
 | |
| 	bDQuote         = []byte{'"'} // quote signal
 | |
| 	sectionStart    = []byte{'['} // section start signal
 | |
| 	sectionEnd      = []byte{']'} // section end signal
 | |
| )
 | |
| 
 | |
| // IniConfig implements Config to parse ini file.
 | |
| 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[string]map[string]string),
 | |
| 		make(map[string]string),
 | |
| 		make(map[string]string),
 | |
| 		sync.RWMutex{},
 | |
| 	}
 | |
| 	cfg.Lock()
 | |
| 	defer cfg.Unlock()
 | |
| 	defer file.Close()
 | |
| 
 | |
| 	var comment bytes.Buffer
 | |
| 	buf := bufio.NewReader(file)
 | |
| 	section := DEFAULT_SECTION
 | |
| 	for {
 | |
| 		line, _, err := buf.ReadLine()
 | |
| 		if err == io.EOF {
 | |
| 			break
 | |
| 		}
 | |
| 		if bytes.Equal(line, bEmpty) {
 | |
| 			continue
 | |
| 		}
 | |
| 		line = bytes.TrimSpace(line)
 | |
| 
 | |
| 		var bComment []byte
 | |
| 		switch {
 | |
| 		case bytes.HasPrefix(line, bNumComment):
 | |
| 			bComment = bNumComment
 | |
| 		case bytes.HasPrefix(line, bSemComment):
 | |
| 			bComment = bSemComment
 | |
| 		}
 | |
| 		if bComment != nil {
 | |
| 			line = bytes.TrimLeft(line, string(bComment))
 | |
| 			line = bytes.TrimLeftFunc(line, unicode.IsSpace)
 | |
| 			comment.Write(line)
 | |
| 			comment.WriteByte('\n')
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
 | |
| 			section = string(line[1 : len(line)-1])
 | |
| 			section = strings.ToLower(section) // section name case insensitive
 | |
| 			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, `"`)
 | |
| 			}
 | |
| 
 | |
| 			key := string(bytes.TrimSpace(keyval[0])) // key name case insensitive
 | |
| 			key = strings.ToLower(key)
 | |
| 			cfg.data[section][key] = string(val)
 | |
| 			if comment.Len() > 0 {
 | |
| 				cfg.keycomment[section+"."+key] = comment.String()
 | |
| 				comment.Reset()
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 	return cfg, nil
 | |
| }
 | |
| 
 | |
| // A Config represents the ini configuration.
 | |
| // When set and get value, support key as section:name type.
 | |
| type IniConfigContainer struct {
 | |
| 	filename       string
 | |
| 	data           map[string]map[string]string // section=> key:val
 | |
| 	sectionComment map[string]string            // section : comment
 | |
| 	keycomment     map[string]string            // id: []{comment, key...}; id 1 is for main comment.
 | |
| 	sync.RWMutex
 | |
| }
 | |
| 
 | |
| // Bool returns the boolean value for a given key.
 | |
| func (c *IniConfigContainer) Bool(key string) (bool, error) {
 | |
| 	key = strings.ToLower(key)
 | |
| 	return strconv.ParseBool(c.getdata(key))
 | |
| }
 | |
| 
 | |
| // Int returns the integer value for a given key.
 | |
| func (c *IniConfigContainer) Int(key string) (int, error) {
 | |
| 	key = strings.ToLower(key)
 | |
| 	return strconv.Atoi(c.getdata(key))
 | |
| }
 | |
| 
 | |
| // Int64 returns the int64 value for a given key.
 | |
| func (c *IniConfigContainer) Int64(key string) (int64, error) {
 | |
| 	key = strings.ToLower(key)
 | |
| 	return strconv.ParseInt(c.getdata(key), 10, 64)
 | |
| }
 | |
| 
 | |
| // Float returns the float value for a given key.
 | |
| func (c *IniConfigContainer) Float(key string) (float64, error) {
 | |
| 	key = strings.ToLower(key)
 | |
| 	return strconv.ParseFloat(c.getdata(key), 64)
 | |
| }
 | |
| 
 | |
| // String returns the string value for a given key.
 | |
| func (c *IniConfigContainer) String(key string) string {
 | |
| 	key = strings.ToLower(key)
 | |
| 	return c.getdata(key)
 | |
| }
 | |
| 
 | |
| // Strings returns the []string value for a given key.
 | |
| func (c *IniConfigContainer) Strings(key string) []string {
 | |
| 	return strings.Split(c.String(key), ";")
 | |
| }
 | |
| 
 | |
| // WriteValue writes a new value for key.
 | |
| // if write to one section, the key need be "section::key".
 | |
| // if the section is not existed, it panics.
 | |
| func (c *IniConfigContainer) Set(key, value string) error {
 | |
| 	c.Lock()
 | |
| 	defer c.Unlock()
 | |
| 	if len(key) == 0 {
 | |
| 		return errors.New("key is empty")
 | |
| 	}
 | |
| 
 | |
| 	var section, k string
 | |
| 	key = strings.ToLower(key)
 | |
| 	sectionkey := strings.Split(key, "::")
 | |
| 	if len(sectionkey) >= 2 {
 | |
| 		section = sectionkey[0]
 | |
| 		k = sectionkey[1]
 | |
| 	} else {
 | |
| 		section = DEFAULT_SECTION
 | |
| 		k = sectionkey[0]
 | |
| 	}
 | |
| 	if _, ok := c.data[section]; !ok {
 | |
| 		c.data[section] = make(map[string]string)
 | |
| 	}
 | |
| 	c.data[section][k] = value
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // DIY returns the raw value by a given key.
 | |
| func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
 | |
| 	key = strings.ToLower(key)
 | |
| 	if v, ok := c.data[key]; ok {
 | |
| 		return v, nil
 | |
| 	}
 | |
| 	return v, errors.New("key not find")
 | |
| }
 | |
| 
 | |
| // section.key or key
 | |
| func (c *IniConfigContainer) getdata(key string) string {
 | |
| 	c.RLock()
 | |
| 	defer c.RUnlock()
 | |
| 	if len(key) == 0 {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	var section, k string
 | |
| 	key = strings.ToLower(key)
 | |
| 	sectionkey := strings.Split(key, "::")
 | |
| 	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 ""
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	Register("ini", &IniConfig{})
 | |
| }
 |