mirror of
https://github.com/astaxie/beego.git
synced 2024-11-16 17:20:55 +00:00
be544f963e
ParseBool returns the boolean value represented by the string. It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On, 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off. Any other value returns an error.
438 lines
11 KiB
Go
438 lines
11 KiB
Go
// Copyright 2014 beego Author. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
"unicode"
|
|
)
|
|
|
|
var (
|
|
defaultSection = "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
|
|
lineBreak = "\n"
|
|
)
|
|
|
|
// IniConfig implements Config to parse ini file.
|
|
type IniConfig struct {
|
|
}
|
|
|
|
// Parse creates a new Config and parses the file configuration from the named file.
|
|
func (ini *IniConfig) Parse(name string) (Configer, error) {
|
|
return ini.parseFile(name)
|
|
}
|
|
|
|
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, 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)
|
|
// check the BOM
|
|
head, err := buf.Peek(3)
|
|
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
|
|
for i := 1; i <= 3; i++ {
|
|
buf.ReadByte()
|
|
}
|
|
}
|
|
section := defaultSection
|
|
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 = strings.ToLower(string(line[1 : len(line)-1])) // 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)
|
|
}
|
|
continue
|
|
}
|
|
|
|
if _, ok := cfg.data[section]; !ok {
|
|
cfg.data[section] = make(map[string]string)
|
|
}
|
|
keyValue := bytes.SplitN(line, bEqual, 2)
|
|
|
|
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
|
|
key = strings.ToLower(key)
|
|
|
|
// handle include "other.conf"
|
|
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
|
|
includefiles := strings.Fields(key)
|
|
if includefiles[0] == "include" && len(includefiles) == 2 {
|
|
otherfile := strings.Trim(includefiles[1], "\"")
|
|
if !path.IsAbs(otherfile) {
|
|
otherfile = path.Join(path.Dir(name), otherfile)
|
|
}
|
|
i, err := ini.parseFile(otherfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for sec, dt := range i.data {
|
|
if _, ok := cfg.data[sec]; !ok {
|
|
cfg.data[sec] = make(map[string]string)
|
|
}
|
|
for k, v := range dt {
|
|
cfg.data[sec][k] = v
|
|
}
|
|
}
|
|
for sec, comm := range i.sectionComment {
|
|
cfg.sectionComment[sec] = comm
|
|
}
|
|
for k, comm := range i.keyComment {
|
|
cfg.keyComment[k] = comm
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
if len(keyValue) != 2 {
|
|
return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
|
|
}
|
|
val := bytes.TrimSpace(keyValue[1])
|
|
if bytes.HasPrefix(val, bDQuote) {
|
|
val = bytes.Trim(val, `"`)
|
|
}
|
|
|
|
cfg.data[section][key] = string(val)
|
|
if comment.Len() > 0 {
|
|
cfg.keyComment[section+"."+key] = comment.String()
|
|
comment.Reset()
|
|
}
|
|
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
// ParseData parse ini the data
|
|
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
|
|
// Save memory data to temporary file
|
|
tmpName := path.Join(os.TempDir(), "beego", fmt.Sprintf("%d", time.Now().Nanosecond()))
|
|
os.MkdirAll(path.Dir(tmpName), os.ModePerm)
|
|
if err := ioutil.WriteFile(tmpName, data, 0655); err != nil {
|
|
return nil, err
|
|
}
|
|
return ini.Parse(tmpName)
|
|
}
|
|
|
|
// IniConfigContainer 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) {
|
|
return ParseBool(c.getdata(key))
|
|
}
|
|
|
|
// DefaultBool returns the boolean value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
|
v, err := c.Bool(key)
|
|
if err != nil {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Int returns the integer value for a given key.
|
|
func (c *IniConfigContainer) Int(key string) (int, error) {
|
|
return strconv.Atoi(c.getdata(key))
|
|
}
|
|
|
|
// DefaultInt returns the integer value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
|
v, err := c.Int(key)
|
|
if err != nil {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Int64 returns the int64 value for a given key.
|
|
func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
|
return strconv.ParseInt(c.getdata(key), 10, 64)
|
|
}
|
|
|
|
// DefaultInt64 returns the int64 value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
|
v, err := c.Int64(key)
|
|
if err != nil {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Float returns the float value for a given key.
|
|
func (c *IniConfigContainer) Float(key string) (float64, error) {
|
|
return strconv.ParseFloat(c.getdata(key), 64)
|
|
}
|
|
|
|
// DefaultFloat returns the float64 value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
|
v, err := c.Float(key)
|
|
if err != nil {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// String returns the string value for a given key.
|
|
func (c *IniConfigContainer) String(key string) string {
|
|
return c.getdata(key)
|
|
}
|
|
|
|
// DefaultString returns the string value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
|
v := c.String(key)
|
|
if v == "" {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Strings returns the []string value for a given key.
|
|
func (c *IniConfigContainer) Strings(key string) []string {
|
|
return strings.Split(c.String(key), ";")
|
|
}
|
|
|
|
// DefaultStrings returns the []string value for a given key.
|
|
// if err != nil return defaltval
|
|
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
|
v := c.Strings(key)
|
|
if len(v) == 0 {
|
|
return defaultval
|
|
}
|
|
return v
|
|
}
|
|
|
|
// GetSection returns map for the given section
|
|
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
|
|
if v, ok := c.data[section]; ok {
|
|
return v, nil
|
|
}
|
|
return nil, errors.New("not exist setction")
|
|
}
|
|
|
|
// SaveConfigFile save the config into file
|
|
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
|
// Write configuration file by filename.
|
|
f, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
// Save default section at first place
|
|
if dt, ok := c.data[defaultSection]; ok {
|
|
for key, val := range dt {
|
|
if key != " " {
|
|
// Write key comments.
|
|
if v, ok := c.keyComment[key]; ok {
|
|
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write key and value.
|
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put a line between sections.
|
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Save named sections
|
|
for section, dt := range c.data {
|
|
if section != defaultSection {
|
|
// Write section comments.
|
|
if v, ok := c.sectionComment[section]; ok {
|
|
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write section name.
|
|
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
|
|
for key, val := range dt {
|
|
if key != " " {
|
|
// Write key comments.
|
|
if v, ok := c.keyComment[key]; ok {
|
|
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write key and value.
|
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put a line between sections.
|
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, err = buf.WriteTo(f); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Set 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
|
|
sectionKey = strings.Split(key, "::")
|
|
)
|
|
|
|
if len(sectionKey) >= 2 {
|
|
section = sectionKey[0]
|
|
k = sectionKey[1]
|
|
} else {
|
|
section = defaultSection
|
|
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) {
|
|
if v, ok := c.data[strings.ToLower(key)]; ok {
|
|
return v, nil
|
|
}
|
|
return v, errors.New("key not find")
|
|
}
|
|
|
|
// section.key or key
|
|
func (c *IniConfigContainer) getdata(key string) string {
|
|
if len(key) == 0 {
|
|
return ""
|
|
}
|
|
c.RLock()
|
|
defer c.RUnlock()
|
|
|
|
var (
|
|
section, k string
|
|
sectionKey = strings.Split(strings.ToLower(key), "::")
|
|
)
|
|
if len(sectionKey) >= 2 {
|
|
section = sectionKey[0]
|
|
k = sectionKey[1]
|
|
} else {
|
|
section = defaultSection
|
|
k = sectionKey[0]
|
|
}
|
|
if v, ok := c.data[section]; ok {
|
|
if vv, ok := v[k]; ok {
|
|
return vv
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func init() {
|
|
Register("ini", &IniConfig{})
|
|
}
|