123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- // 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
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "strings"
- "time"
- )
- // Read reads an io.Reader and returns a configuration representation.
- // This representation can be queried with GetValue.
- func (c *ConfigFile) read(reader io.Reader) (err error) {
- buf := bufio.NewReader(reader)
- // Handle BOM-UTF8.
- // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
- mask, err := buf.Peek(3)
- if err == nil && len(mask) >= 3 &&
- mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
- buf.Read(mask)
- }
- count := 1 // Counter for auto increment.
- // Current section name.
- section := DEFAULT_SECTION
- var comments string
- // Parse line-by-line
- for {
- line, err := buf.ReadString('\n')
- line = strings.TrimSpace(line)
- lineLengh := len(line) //[SWH|+]
- if err != nil {
- if err != io.EOF {
- return err
- }
- // Reached end of file, if nothing to read then break,
- // otherwise handle the last line.
- if lineLengh == 0 {
- break
- }
- }
- // switch written for readability (not performance)
- switch {
- case lineLengh == 0: // Empty line
- continue
- case line[0] == '#' || line[0] == ';': // Comment
- // Append comments
- if len(comments) == 0 {
- comments = line
- } else {
- comments += LineBreak + line
- }
- continue
- case line[0] == '[' && line[lineLengh-1] == ']': // New sction.
- // Get section name.
- section = strings.TrimSpace(line[1 : lineLengh-1])
- // Set section comments and empty if it has comments.
- if len(comments) > 0 {
- c.SetSectionComments(section, comments)
- comments = ""
- }
- // Make section exist even though it does not have any key.
- c.SetValue(section, " ", " ")
- // Reset counter.
- count = 1
- continue
- case section == "": // No section defined so far
- return readError{ERR_BLANK_SECTION_NAME, line}
- default: // Other alternatives
- var (
- i int
- keyQuote string
- key string
- valQuote string
- value string
- )
- //[SWH|+]:支持引号包围起来的字串
- if line[0] == '"' {
- if lineLengh >= 6 && line[0:3] == `"""` {
- keyQuote = `"""`
- } else {
- keyQuote = `"`
- }
- } else if line[0] == '`' {
- keyQuote = "`"
- }
- if keyQuote != "" {
- qLen := len(keyQuote)
- pos := strings.Index(line[qLen:], keyQuote)
- if pos == -1 {
- return readError{ERR_COULD_NOT_PARSE, line}
- }
- pos = pos + qLen
- i = strings.IndexAny(line[pos:], "=:")
- if i <= 0 {
- return readError{ERR_COULD_NOT_PARSE, line}
- }
- i = i + pos
- key = line[qLen:pos] //保留引号内的两端的空格
- } else {
- i = strings.IndexAny(line, "=:")
- if i <= 0 {
- return readError{ERR_COULD_NOT_PARSE, line}
- }
- key = strings.TrimSpace(line[0:i])
- }
- //[SWH|+];
- // Check if it needs auto increment.
- if key == "-" {
- key = "#" + fmt.Sprint(count)
- count++
- }
- //[SWH|+]:支持引号包围起来的字串
- lineRight := strings.TrimSpace(line[i+1:])
- lineRightLength := len(lineRight)
- firstChar := ""
- if lineRightLength >= 2 {
- firstChar = lineRight[0:1]
- }
- if firstChar == "`" {
- valQuote = "`"
- } else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
- valQuote = `"""`
- }
- if valQuote != "" {
- qLen := len(valQuote)
- pos := strings.LastIndex(lineRight[qLen:], valQuote)
- if pos == -1 {
- return readError{ERR_COULD_NOT_PARSE, line}
- }
- pos = pos + qLen
- value = lineRight[qLen:pos]
- } else {
- value = strings.TrimSpace(lineRight[0:])
- }
- //[SWH|+];
- c.SetValue(section, key, value)
- // Set key comments and empty if it has comments.
- if len(comments) > 0 {
- c.SetKeyComments(section, key, comments)
- comments = ""
- }
- }
- // Reached end of file.
- if err == io.EOF {
- break
- }
- }
- return nil
- }
- // LoadFromData accepts raw data directly from memory
- // and returns a new configuration representation.
- // Note that the configuration is written to the system
- // temporary folder, so your file should not contain
- // sensitive information.
- func LoadFromData(data []byte) (c *ConfigFile, err error) {
- // Save memory data to temporary file to support further operations.
- tmpName := path.Join(os.TempDir(), "goconfig", fmt.Sprintf("%d", time.Now().Nanosecond()))
- if err = os.MkdirAll(path.Dir(tmpName), os.ModePerm); err != nil {
- return nil, err
- }
- if err = ioutil.WriteFile(tmpName, data, 0655); err != nil {
- return nil, err
- }
- c = newConfigFile([]string{tmpName})
- err = c.read(bytes.NewBuffer(data))
- return c, err
- }
- // LoadFromReader accepts raw data directly from a reader
- // and returns a new configuration representation.
- // You must use ReloadData to reload.
- // You cannot append files a configfile read this way.
- func LoadFromReader(in io.Reader) (c *ConfigFile, err error) {
- c = newConfigFile([]string{""})
- err = c.read(in)
- return c, err
- }
- func (c *ConfigFile) loadFile(fileName string) (err error) {
- f, err := os.Open(fileName)
- if err != nil {
- return err
- }
- defer f.Close()
- return c.read(f)
- }
- // LoadConfigFile reads a file and returns a new configuration representation.
- // This representation can be queried with GetValue.
- func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) {
- // Append files' name together.
- fileNames := make([]string, 1, len(moreFiles)+1)
- fileNames[0] = fileName
- if len(moreFiles) > 0 {
- fileNames = append(fileNames, moreFiles...)
- }
- c = newConfigFile(fileNames)
- for _, name := range fileNames {
- if err = c.loadFile(name); err != nil {
- return nil, err
- }
- }
- return c, nil
- }
- // Reload reloads configuration file in case it has changes.
- func (c *ConfigFile) Reload() (err error) {
- var cfg *ConfigFile
- if len(c.fileNames) == 1 {
- if c.fileNames[0] == "" {
- return fmt.Errorf("file opened from in-memory data, use ReloadData to reload")
- }
- cfg, err = LoadConfigFile(c.fileNames[0])
- } else {
- cfg, err = LoadConfigFile(c.fileNames[0], c.fileNames[1:]...)
- }
- if err == nil {
- *c = *cfg
- }
- return err
- }
- // ReloadData reloads configuration file from memory
- func (c *ConfigFile) ReloadData(in io.Reader) (err error) {
- var cfg *ConfigFile
- if len(c.fileNames) != 1 {
- return fmt.Errorf("Multiple files loaded, unable to mix in-memory and file data")
- }
- cfg, err = LoadFromReader(in)
- if err == nil {
- *c = *cfg
- }
- return err
- }
- // AppendFiles appends more files to ConfigFile and reload automatically.
- func (c *ConfigFile) AppendFiles(files ...string) error {
- if len(c.fileNames) == 1 && c.fileNames[0] == "" {
- return fmt.Errorf("Cannot append file data to in-memory data")
- }
- c.fileNames = append(c.fileNames, files...)
- return c.Reload()
- }
- // readError occurs when read configuration file with wrong format.
- type readError struct {
- Reason ParseError
- Content string // Line content
- }
- // Error implement Error interface.
- func (err readError) Error() string {
- switch err.Reason {
- case ERR_BLANK_SECTION_NAME:
- return "empty section name not allowed"
- case ERR_COULD_NOT_PARSE:
- return fmt.Sprintf("could not parse line: %s", string(err.Content))
- }
- return "invalid read error"
- }
|