123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- // Copyright 2013 Unknwon
- //
- // 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 goconfig is a fully functional and comments-support configuration file(.ini) parser.
- package goconfig
- import (
- "fmt"
- "regexp"
- "runtime"
- "strconv"
- "strings"
- "sync"
- )
- const (
- // Default section name.
- DEFAULT_SECTION = "DEFAULT"
- // Maximum allowed depth when recursively substituing variable names.
- _DEPTH_VALUES = 200
- )
- type ParseError int
- const (
- ERR_SECTION_NOT_FOUND ParseError = iota + 1
- ERR_KEY_NOT_FOUND
- ERR_BLANK_SECTION_NAME
- ERR_COULD_NOT_PARSE
- )
- var LineBreak = "\n"
- // Variable regexp pattern: %(variable)s
- var varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
- func init() {
- if runtime.GOOS == "windows" {
- LineBreak = "\r\n"
- }
- }
- // A ConfigFile represents a INI formar configuration file.
- type ConfigFile struct {
- lock sync.RWMutex // Go map is not safe.
- fileNames []string // Support mutil-files.
- data map[string]map[string]string // Section -> key : value
- // Lists can keep sections and keys in order.
- sectionList []string // Section name list.
- keyList map[string][]string // Section -> Key name list
- sectionComments map[string]string // Sections comments.
- keyComments map[string]map[string]string // Keys comments.
- BlockMode bool // Indicates whether use lock or not.
- }
- // newConfigFile creates an empty configuration representation.
- func newConfigFile(fileNames []string) *ConfigFile {
- c := new(ConfigFile)
- c.fileNames = fileNames
- c.data = make(map[string]map[string]string)
- c.keyList = make(map[string][]string)
- c.sectionComments = make(map[string]string)
- c.keyComments = make(map[string]map[string]string)
- c.BlockMode = true
- return c
- }
- // SetValue adds a new section-key-value to the configuration.
- // It returns true if the key and value were inserted,
- // or returns false if the value was overwritten.
- // If the section does not exist in advance, it will be created.
- func (c *ConfigFile) SetValue(section, key, value string) bool {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if len(key) == 0 {
- return false
- }
- if c.BlockMode {
- c.lock.Lock()
- defer c.lock.Unlock()
- }
- // Check if section exists.
- if _, ok := c.data[section]; !ok {
- // Execute add operation.
- c.data[section] = make(map[string]string)
- // Append section to list.
- c.sectionList = append(c.sectionList, section)
- }
- // Check if key exists.
- _, ok := c.data[section][key]
- c.data[section][key] = value
- if !ok {
- // If not exists, append to key list.
- c.keyList[section] = append(c.keyList[section], key)
- }
- return !ok
- }
- // DeleteKey deletes the key in given section.
- // It returns true if the key was deleted,
- // or returns false if the section or key didn't exist.
- func (c *ConfigFile) DeleteKey(section, key string) bool {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if c.BlockMode {
- c.lock.Lock()
- defer c.lock.Unlock()
- }
- // Check if section exists.
- if _, ok := c.data[section]; !ok {
- return false
- }
- // Check if key exists.
- if _, ok := c.data[section][key]; ok {
- delete(c.data[section], key)
- // Remove comments of key.
- c.SetKeyComments(section, key, "")
- // Get index of key.
- i := 0
- for _, keyName := range c.keyList[section] {
- if keyName == key {
- break
- }
- i++
- }
- // Remove from key list.
- c.keyList[section] = append(c.keyList[section][:i], c.keyList[section][i+1:]...)
- return true
- }
- return false
- }
- // GetValue returns the value of key available in the given section.
- // If the value needs to be unfolded
- // (see e.g. %(google)s example in the GoConfig_test.go),
- // then String does this unfolding automatically, up to
- // _DEPTH_VALUES number of iterations.
- // It returns an error and empty string value if the section does not exist,
- // or key does not exist in DEFAULT and current sections.
- func (c *ConfigFile) GetValue(section, key string) (string, error) {
- if c.BlockMode {
- c.lock.RLock()
- defer c.lock.RUnlock()
- }
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- // Check if section exists
- if _, ok := c.data[section]; !ok {
- // Section does not exist.
- return "", getError{ERR_SECTION_NOT_FOUND, section}
- }
- // Section exists.
- // Check if key exists or empty value.
- value, ok := c.data[section][key]
- if !ok {
- // Check if it is a sub-section.
- if i := strings.LastIndex(section, "."); i > -1 {
- return c.GetValue(section[:i], key)
- }
- // Return empty value.
- return "", getError{ERR_KEY_NOT_FOUND, key}
- }
- // Key exists.
- var i int
- for i = 0; i < _DEPTH_VALUES; i++ {
- vr := varPattern.FindString(value)
- if len(vr) == 0 {
- break
- }
- // Take off leading '%(' and trailing ')s'.
- noption := strings.TrimLeft(vr, "%(")
- noption = strings.TrimRight(noption, ")s")
- // Search variable in default section.
- nvalue, err := c.GetValue(DEFAULT_SECTION, noption)
- if err != nil && section != DEFAULT_SECTION {
- // Search in the same section.
- if _, ok := c.data[section][noption]; ok {
- nvalue = c.data[section][noption]
- }
- }
- // Substitute by new value and take off leading '%(' and trailing ')s'.
- value = strings.Replace(value, vr, nvalue, -1)
- }
- return value, nil
- }
- // Bool returns bool type value.
- func (c *ConfigFile) Bool(section, key string) (bool, error) {
- value, err := c.GetValue(section, key)
- if err != nil {
- return false, err
- }
- return strconv.ParseBool(value)
- }
- // Float64 returns float64 type value.
- func (c *ConfigFile) Float64(section, key string) (float64, error) {
- value, err := c.GetValue(section, key)
- if err != nil {
- return 0.0, err
- }
- return strconv.ParseFloat(value, 64)
- }
- // Int returns int type value.
- func (c *ConfigFile) Int(section, key string) (int, error) {
- value, err := c.GetValue(section, key)
- if err != nil {
- return 0, err
- }
- return strconv.Atoi(value)
- }
- // Int64 returns int64 type value.
- func (c *ConfigFile) Int64(section, key string) (int64, error) {
- value, err := c.GetValue(section, key)
- if err != nil {
- return 0, err
- }
- return strconv.ParseInt(value, 10, 64)
- }
- // MustValue always returns value without error.
- // It returns empty string if error occurs, or the default value if given.
- func (c *ConfigFile) MustValue(section, key string, defaultVal ...string) string {
- val, err := c.GetValue(section, key)
- if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
- return defaultVal[0]
- }
- return val
- }
- // MustValue always returns value without error,
- // It returns empty string if error occurs, or the default value if given,
- // and a bool value indicates whether default value is returned.
- func (c *ConfigFile) MustValueSet(section, key string, defaultVal ...string) (string, bool) {
- val, err := c.GetValue(section, key)
- if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
- c.SetValue(section, key, defaultVal[0])
- return defaultVal[0], true
- }
- return val, false
- }
- // MustValueRange always returns value without error,
- // it returns default value if error occurs or doesn't fit into range.
- func (c *ConfigFile) MustValueRange(section, key, defaultVal string, candidates []string) string {
- val, err := c.GetValue(section, key)
- if err != nil || len(val) == 0 {
- return defaultVal
- }
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
- }
- // MustValueArray always returns value array without error,
- // it returns empty array if error occurs, split by delimiter otherwise.
- func (c *ConfigFile) MustValueArray(section, key, delim string) []string {
- val, err := c.GetValue(section, key)
- if err != nil || len(val) == 0 {
- return []string{}
- }
- vals := strings.Split(val, delim)
- for i := range vals {
- vals[i] = strings.TrimSpace(vals[i])
- }
- return vals
- }
- // MustBool always returns value without error,
- // it returns false if error occurs.
- func (c *ConfigFile) MustBool(section, key string, defaultVal ...bool) bool {
- val, err := c.Bool(section, key)
- if len(defaultVal) > 0 && err != nil {
- return defaultVal[0]
- }
- return val
- }
- // MustFloat64 always returns value without error,
- // it returns 0.0 if error occurs.
- func (c *ConfigFile) MustFloat64(section, key string, defaultVal ...float64) float64 {
- value, err := c.Float64(section, key)
- if len(defaultVal) > 0 && err != nil {
- return defaultVal[0]
- }
- return value
- }
- // MustInt always returns value without error,
- // it returns 0 if error occurs.
- func (c *ConfigFile) MustInt(section, key string, defaultVal ...int) int {
- value, err := c.Int(section, key)
- if len(defaultVal) > 0 && err != nil {
- return defaultVal[0]
- }
- return value
- }
- // MustInt64 always returns value without error,
- // it returns 0 if error occurs.
- func (c *ConfigFile) MustInt64(section, key string, defaultVal ...int64) int64 {
- value, err := c.Int64(section, key)
- if len(defaultVal) > 0 && err != nil {
- return defaultVal[0]
- }
- return value
- }
- // GetSectionList returns the list of all sections
- // in the same order in the file.
- func (c *ConfigFile) GetSectionList() []string {
- list := make([]string, len(c.sectionList))
- copy(list, c.sectionList)
- return list
- }
- // GetKeyList returns the list of all keys in give section
- // in the same order in the file.
- // It returns nil if given section does not exist.
- func (c *ConfigFile) GetKeyList(section string) []string {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if c.BlockMode {
- c.lock.RLock()
- defer c.lock.RUnlock()
- }
- // Check if section exists.
- if _, ok := c.data[section]; !ok {
- return nil
- }
- // Non-default section has a blank key as section keeper.
- offset := 1
- if section == DEFAULT_SECTION {
- offset = 0
- }
- list := make([]string, len(c.keyList[section])-offset)
- copy(list, c.keyList[section][offset:])
- return list
- }
- // DeleteSection deletes the entire section by given name.
- // It returns true if the section was deleted, and false if the section didn't exist.
- func (c *ConfigFile) DeleteSection(section string) bool {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if c.BlockMode {
- c.lock.Lock()
- defer c.lock.Unlock()
- }
- // Check if section exists.
- if _, ok := c.data[section]; !ok {
- return false
- }
- delete(c.data, section)
- // Remove comments of section.
- c.SetSectionComments(section, "")
- // Get index of section.
- i := 0
- for _, secName := range c.sectionList {
- if secName == section {
- break
- }
- i++
- }
- // Remove from section and key list.
- c.sectionList = append(c.sectionList[:i], c.sectionList[i+1:]...)
- delete(c.keyList, section)
- return true
- }
- // GetSection returns key-value pairs in given section.
- // It section does not exist, returns nil and error.
- func (c *ConfigFile) GetSection(section string) (map[string]string, error) {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if c.BlockMode {
- c.lock.Lock()
- defer c.lock.Unlock()
- }
- // Check if section exists.
- if _, ok := c.data[section]; !ok {
- // Section does not exist.
- return nil, getError{ERR_SECTION_NOT_FOUND, section}
- }
- // Remove pre-defined key.
- secMap := c.data[section]
- delete(c.data[section], " ")
- // Section exists.
- return secMap, nil
- }
- // SetSectionComments adds new section comments to the configuration.
- // If comments are empty(0 length), it will remove its section comments!
- // It returns true if the comments were inserted or removed,
- // or returns false if the comments were overwritten.
- func (c *ConfigFile) SetSectionComments(section, comments string) bool {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if len(comments) == 0 {
- if _, ok := c.sectionComments[section]; ok {
- delete(c.sectionComments, section)
- }
- // Not exists can be seen as remove.
- return true
- }
- // Check if comments exists.
- _, ok := c.sectionComments[section]
- if comments[0] != '#' && comments[0] != ';' {
- comments = "; " + comments
- }
- c.sectionComments[section] = comments
- return !ok
- }
- // SetKeyComments adds new section-key comments to the configuration.
- // If comments are empty(0 length), it will remove its section-key comments!
- // It returns true if the comments were inserted or removed,
- // or returns false if the comments were overwritten.
- // If the section does not exist in advance, it is created.
- func (c *ConfigFile) SetKeyComments(section, key, comments string) bool {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- // Check if section exists.
- if _, ok := c.keyComments[section]; ok {
- if len(comments) == 0 {
- if _, ok := c.keyComments[section][key]; ok {
- delete(c.keyComments[section], key)
- }
- // Not exists can be seen as remove.
- return true
- }
- } else {
- if len(comments) == 0 {
- // Not exists can be seen as remove.
- return true
- } else {
- // Execute add operation.
- c.keyComments[section] = make(map[string]string)
- }
- }
- // Check if key exists.
- _, ok := c.keyComments[section][key]
- if comments[0] != '#' && comments[0] != ';' {
- comments = "; " + comments
- }
- c.keyComments[section][key] = comments
- return !ok
- }
- // GetSectionComments returns the comments in the given section.
- // It returns an empty string(0 length) if the comments do not exist.
- func (c *ConfigFile) GetSectionComments(section string) (comments string) {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- return c.sectionComments[section]
- }
- // GetKeyComments returns the comments of key in the given section.
- // It returns an empty string(0 length) if the comments do not exist.
- func (c *ConfigFile) GetKeyComments(section, key string) (comments string) {
- // Blank section name represents DEFAULT section.
- if len(section) == 0 {
- section = DEFAULT_SECTION
- }
- if _, ok := c.keyComments[section]; ok {
- return c.keyComments[section][key]
- }
- return ""
- }
- // getError occurs when get value in configuration file with invalid parameter.
- type getError struct {
- Reason ParseError
- Name string
- }
- // Error implements Error interface.
- func (err getError) Error() string {
- switch err.Reason {
- case ERR_SECTION_NOT_FOUND:
- return fmt.Sprintf("section '%s' not found", err.Name)
- case ERR_KEY_NOT_FOUND:
- return fmt.Sprintf("key '%s' not found", err.Name)
- }
- return "invalid get error"
- }
|