]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/go-ini/ini/parser.go
Merge pull request #27 from terraform-providers/go-modules-2019-02-22
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / go-ini / ini / parser.go
1 // Copyright 2015 Unknwon
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 // not use this file except in compliance with the License. You may obtain
5 // a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations
13 // under the License.
14
15 package ini
16
17 import (
18 "bufio"
19 "bytes"
20 "fmt"
21 "io"
22 "strconv"
23 "strings"
24 "unicode"
25 )
26
27 type tokenType int
28
29 const (
30 _TOKEN_INVALID tokenType = iota
31 _TOKEN_COMMENT
32 _TOKEN_SECTION
33 _TOKEN_KEY
34 )
35
36 type parser struct {
37 buf *bufio.Reader
38 isEOF bool
39 count int
40 comment *bytes.Buffer
41 }
42
43 func newParser(r io.Reader) *parser {
44 return &parser{
45 buf: bufio.NewReader(r),
46 count: 1,
47 comment: &bytes.Buffer{},
48 }
49 }
50
51 // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
52 // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
53 func (p *parser) BOM() error {
54 mask, err := p.buf.Peek(2)
55 if err != nil && err != io.EOF {
56 return err
57 } else if len(mask) < 2 {
58 return nil
59 }
60
61 switch {
62 case mask[0] == 254 && mask[1] == 255:
63 fallthrough
64 case mask[0] == 255 && mask[1] == 254:
65 p.buf.Read(mask)
66 case mask[0] == 239 && mask[1] == 187:
67 mask, err := p.buf.Peek(3)
68 if err != nil && err != io.EOF {
69 return err
70 } else if len(mask) < 3 {
71 return nil
72 }
73 if mask[2] == 191 {
74 p.buf.Read(mask)
75 }
76 }
77 return nil
78 }
79
80 func (p *parser) readUntil(delim byte) ([]byte, error) {
81 data, err := p.buf.ReadBytes(delim)
82 if err != nil {
83 if err == io.EOF {
84 p.isEOF = true
85 } else {
86 return nil, err
87 }
88 }
89 return data, nil
90 }
91
92 func cleanComment(in []byte) ([]byte, bool) {
93 i := bytes.IndexAny(in, "#;")
94 if i == -1 {
95 return nil, false
96 }
97 return in[i:], true
98 }
99
100 func readKeyName(in []byte) (string, int, error) {
101 line := string(in)
102
103 // Check if key name surrounded by quotes.
104 var keyQuote string
105 if line[0] == '"' {
106 if len(line) > 6 && string(line[0:3]) == `"""` {
107 keyQuote = `"""`
108 } else {
109 keyQuote = `"`
110 }
111 } else if line[0] == '`' {
112 keyQuote = "`"
113 }
114
115 // Get out key name
116 endIdx := -1
117 if len(keyQuote) > 0 {
118 startIdx := len(keyQuote)
119 // FIXME: fail case -> """"""name"""=value
120 pos := strings.Index(line[startIdx:], keyQuote)
121 if pos == -1 {
122 return "", -1, fmt.Errorf("missing closing key quote: %s", line)
123 }
124 pos += startIdx
125
126 // Find key-value delimiter
127 i := strings.IndexAny(line[pos+startIdx:], "=:")
128 if i < 0 {
129 return "", -1, ErrDelimiterNotFound{line}
130 }
131 endIdx = pos + i
132 return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
133 }
134
135 endIdx = strings.IndexAny(line, "=:")
136 if endIdx < 0 {
137 return "", -1, ErrDelimiterNotFound{line}
138 }
139 return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
140 }
141
142 func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
143 for {
144 data, err := p.readUntil('\n')
145 if err != nil {
146 return "", err
147 }
148 next := string(data)
149
150 pos := strings.LastIndex(next, valQuote)
151 if pos > -1 {
152 val += next[:pos]
153
154 comment, has := cleanComment([]byte(next[pos:]))
155 if has {
156 p.comment.Write(bytes.TrimSpace(comment))
157 }
158 break
159 }
160 val += next
161 if p.isEOF {
162 return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
163 }
164 }
165 return val, nil
166 }
167
168 func (p *parser) readContinuationLines(val string) (string, error) {
169 for {
170 data, err := p.readUntil('\n')
171 if err != nil {
172 return "", err
173 }
174 next := strings.TrimSpace(string(data))
175
176 if len(next) == 0 {
177 break
178 }
179 val += next
180 if val[len(val)-1] != '\\' {
181 break
182 }
183 val = val[:len(val)-1]
184 }
185 return val, nil
186 }
187
188 // hasSurroundedQuote check if and only if the first and last characters
189 // are quotes \" or \'.
190 // It returns false if any other parts also contain same kind of quotes.
191 func hasSurroundedQuote(in string, quote byte) bool {
192 return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote &&
193 strings.IndexByte(in[1:], quote) == len(in)-2
194 }
195
196 func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
197 line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
198 if len(line) == 0 {
199 return "", nil
200 }
201
202 var valQuote string
203 if len(line) > 3 && string(line[0:3]) == `"""` {
204 valQuote = `"""`
205 } else if line[0] == '`' {
206 valQuote = "`"
207 }
208
209 if len(valQuote) > 0 {
210 startIdx := len(valQuote)
211 pos := strings.LastIndex(line[startIdx:], valQuote)
212 // Check for multi-line value
213 if pos == -1 {
214 return p.readMultilines(line, line[startIdx:], valQuote)
215 }
216
217 return line[startIdx : pos+startIdx], nil
218 }
219
220 // Won't be able to reach here if value only contains whitespace.
221 line = strings.TrimSpace(line)
222
223 // Check continuation lines when desired.
224 if !ignoreContinuation && line[len(line)-1] == '\\' {
225 return p.readContinuationLines(line[:len(line)-1])
226 }
227
228 i := strings.IndexAny(line, "#;")
229 if i > -1 {
230 p.comment.WriteString(line[i:])
231 line = strings.TrimSpace(line[:i])
232 }
233
234 // Trim single quotes
235 if hasSurroundedQuote(line, '\'') ||
236 hasSurroundedQuote(line, '"') {
237 line = line[1 : len(line)-1]
238 }
239 return line, nil
240 }
241
242 // parse parses data through an io.Reader.
243 func (f *File) parse(reader io.Reader) (err error) {
244 p := newParser(reader)
245 if err = p.BOM(); err != nil {
246 return fmt.Errorf("BOM: %v", err)
247 }
248
249 // Ignore error because default section name is never empty string.
250 section, _ := f.NewSection(DEFAULT_SECTION)
251
252 var line []byte
253 var inUnparseableSection bool
254 for !p.isEOF {
255 line, err = p.readUntil('\n')
256 if err != nil {
257 return err
258 }
259
260 line = bytes.TrimLeftFunc(line, unicode.IsSpace)
261 if len(line) == 0 {
262 continue
263 }
264
265 // Comments
266 if line[0] == '#' || line[0] == ';' {
267 // Note: we do not care ending line break,
268 // it is needed for adding second line,
269 // so just clean it once at the end when set to value.
270 p.comment.Write(line)
271 continue
272 }
273
274 // Section
275 if line[0] == '[' {
276 // Read to the next ']' (TODO: support quoted strings)
277 // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
278 closeIdx := bytes.LastIndex(line, []byte("]"))
279 if closeIdx == -1 {
280 return fmt.Errorf("unclosed section: %s", line)
281 }
282
283 name := string(line[1:closeIdx])
284 section, err = f.NewSection(name)
285 if err != nil {
286 return err
287 }
288
289 comment, has := cleanComment(line[closeIdx+1:])
290 if has {
291 p.comment.Write(comment)
292 }
293
294 section.Comment = strings.TrimSpace(p.comment.String())
295
296 // Reset aotu-counter and comments
297 p.comment.Reset()
298 p.count = 1
299
300 inUnparseableSection = false
301 for i := range f.options.UnparseableSections {
302 if f.options.UnparseableSections[i] == name ||
303 (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
304 inUnparseableSection = true
305 continue
306 }
307 }
308 continue
309 }
310
311 if inUnparseableSection {
312 section.isRawSection = true
313 section.rawBody += string(line)
314 continue
315 }
316
317 kname, offset, err := readKeyName(line)
318 if err != nil {
319 // Treat as boolean key when desired, and whole line is key name.
320 if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
321 kname, err := p.readValue(line, f.options.IgnoreContinuation)
322 if err != nil {
323 return err
324 }
325 key, err := section.NewBooleanKey(kname)
326 if err != nil {
327 return err
328 }
329 key.Comment = strings.TrimSpace(p.comment.String())
330 p.comment.Reset()
331 continue
332 }
333 return err
334 }
335
336 // Auto increment.
337 isAutoIncr := false
338 if kname == "-" {
339 isAutoIncr = true
340 kname = "#" + strconv.Itoa(p.count)
341 p.count++
342 }
343
344 value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
345 if err != nil {
346 return err
347 }
348
349 key, err := section.NewKey(kname, value)
350 if err != nil {
351 return err
352 }
353 key.isAutoIncrement = isAutoIncr
354 key.Comment = strings.TrimSpace(p.comment.String())
355 p.comment.Reset()
356 }
357 return nil
358 }