123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- package goconfig
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "strings"
- "time"
- )
- func (c *ConfigFile) read(reader io.Reader) (err error) {
- buf := bufio.NewReader(reader)
-
-
- 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
-
- section := DEFAULT_SECTION
- var comments string
-
- for {
- line, err := buf.ReadString('\n')
- line = strings.TrimSpace(line)
- lineLengh := len(line)
- if err != nil {
- if err != io.EOF {
- return err
- }
-
-
- if lineLengh == 0 {
- break
- }
- }
-
- switch {
- case lineLengh == 0:
- continue
- case line[0] == '#' || line[0] == ';':
-
- if len(comments) == 0 {
- comments = line
- } else {
- comments += LineBreak + line
- }
- continue
- case line[0] == '[' && line[lineLengh-1] == ']':
-
- section = strings.TrimSpace(line[1 : lineLengh-1])
-
- if len(comments) > 0 {
- c.SetSectionComments(section, comments)
- comments = ""
- }
-
- c.SetValue(section, " ", " ")
-
- count = 1
- continue
- case section == "":
- return readError{ERR_BLANK_SECTION_NAME, line}
- default:
- var (
- i int
- keyQuote string
- key string
- valQuote string
- value string
- )
-
- 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])
- }
-
-
- if key == "-" {
- key = "#" + fmt.Sprint(count)
- count++
- }
-
- 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:])
- }
-
- c.SetValue(section, key, value)
-
- if len(comments) > 0 {
- c.SetKeyComments(section, key, comments)
- comments = ""
- }
- }
-
- if err == io.EOF {
- break
- }
- }
- return nil
- }
- func LoadFromData(data []byte) (c *ConfigFile, err error) {
-
- 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
- }
- 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)
- }
- func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) {
-
- 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
- }
- 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
- }
- 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
- }
- 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()
- }
- type readError struct {
- Reason ParseError
- Content string
- }
- 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"
- }
|