1 /**
2 * // This file is borrowed from https://github.com/vaughan0/go-ini/blob/master/ini.go
3 * // which is distributed under the MIT license (https://github.com/vaughan0/go-ini/blob/master/LICENSE).
4 *
5 * Copyright (c) 2013 Vaughan Newton
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
8 * associated documentation files (the "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
11 * following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial
14 * portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
17 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22 23 // Package config provides functions for parsing INI configuration files.
24 package config
25 26 import (
27 "bufio"
28 "fmt"
29 "io"
30 "os"
31 "regexp"
32 "strings"
33 )
34 35 var (
36 sectionRegex = regexp.MustCompile(`^\[(.*)\]$`)
37 assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`)
38 )
39 40 // ErrSyntax is returned when there is a syntax error in an INI file.
41 type ErrSyntax struct {
42 Line int
43 Source string // The contents of the erroneous line, without leading or trailing whitespace
44 }
45 46 func (e ErrSyntax) Error() string {
47 return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source)
48 }
49 50 // A File represents a parsed INI file.
51 type File map[string]Section
52 53 // A Section represents a single section of an INI file.
54 type Section map[string]string
55 56 // Returns a named Section. A Section will be created if one does not already exist for the given name.
57 func (f File) Section(name string) Section {
58 section := f[name]
59 if section == nil {
60 section = make(Section)
61 f[name] = section
62 }
63 return section
64 }
65 66 // Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup.
67 func (f File) Get(section, key string) (value string, ok bool) {
68 if s := f[section]; s != nil {
69 value, ok = s[key]
70 }
71 return
72 }
73 74 // Loads INI data from a reader and stores the data in the File.
75 func (f File) Load(in io.Reader) error {
76 bufin, ok := in.(*bufio.Reader)
77 if !ok {
78 bufin = bufio.NewReader(in)
79 }
80 return parseFile(bufin, f)
81 }
82 83 // Loads INI data from a named file and stores the data in the File.
84 func (f File) LoadFile(file string) (err error) {
85 in, err := os.Open(file)
86 if err != nil {
87 return
88 }
89 defer in.Close()
90 return f.Load(in)
91 }
92 93 func parseFile(in *bufio.Reader, file File) (err error) {
94 section := ""
95 lineNum := 0
96 for done := false; !done; {
97 var line string
98 if line, err = in.ReadString('\n'); err != nil {
99 if err == io.EOF {
100 done = true
101 } else {
102 return
103 }
104 }
105 lineNum++
106 line = strings.TrimSpace(line)
107 if len(line) == 0 {
108 // Skip blank lines
109 continue
110 }
111 if line[0] == ';' || line[0] == '#' {
112 // Skip comments
113 continue
114 }
115 116 if groups := assignRegex.FindStringSubmatch(line); groups != nil {
117 key, val := groups[1], groups[2]
118 key, val = strings.TrimSpace(key), strings.TrimSpace(val)
119 file.Section(section)[key] = val
120 } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil {
121 name := strings.TrimSpace(groups[1])
122 section = name
123 // Create the section if it does not exist
124 file.Section(section)
125 } else {
126 return ErrSyntax{Line: lineNum, Source: line}
127 }
128 129 }
130 return nil
131 }
132 133 // Loads and returns a File from a reader.
134 func Load(in io.Reader) (File, error) {
135 file := make(File)
136 err := file.Load(in)
137 return file, err
138 }
139 140 // Loads and returns an INI File from a file on disk.
141 func LoadFile(filename string) (File, error) {
142 file := make(File)
143 err := file.LoadFile(filename)
144 return file, err
145 }
146