Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / gopkg.in / ini.v1 / 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         "regexp"
23         "strconv"
24         "strings"
25         "unicode"
26 )
27
28 var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
29
30 type parserOptions struct {
31         IgnoreContinuation          bool
32         IgnoreInlineComment         bool
33         AllowPythonMultilineValues  bool
34         SpaceBeforeInlineComment    bool
35         UnescapeValueDoubleQuotes   bool
36         UnescapeValueCommentSymbols bool
37         PreserveSurroundedQuote     bool
38 }
39
40 type parser struct {
41         buf     *bufio.Reader
42         options parserOptions
43
44         isEOF   bool
45         count   int
46         comment *bytes.Buffer
47 }
48
49 func newParser(r io.Reader, opts parserOptions) *parser {
50         return &parser{
51                 buf:     bufio.NewReader(r),
52                 options: opts,
53                 count:   1,
54                 comment: &bytes.Buffer{},
55         }
56 }
57
58 // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
59 // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
60 func (p *parser) BOM() error {
61         mask, err := p.buf.Peek(2)
62         if err != nil && err != io.EOF {
63                 return err
64         } else if len(mask) < 2 {
65                 return nil
66         }
67
68         switch {
69         case mask[0] == 254 && mask[1] == 255:
70                 fallthrough
71         case mask[0] == 255 && mask[1] == 254:
72                 p.buf.Read(mask)
73         case mask[0] == 239 && mask[1] == 187:
74                 mask, err := p.buf.Peek(3)
75                 if err != nil && err != io.EOF {
76                         return err
77                 } else if len(mask) < 3 {
78                         return nil
79                 }
80                 if mask[2] == 191 {
81                         p.buf.Read(mask)
82                 }
83         }
84         return nil
85 }
86
87 func (p *parser) readUntil(delim byte) ([]byte, error) {
88         data, err := p.buf.ReadBytes(delim)
89         if err != nil {
90                 if err == io.EOF {
91                         p.isEOF = true
92                 } else {
93                         return nil, err
94                 }
95         }
96         return data, nil
97 }
98
99 func cleanComment(in []byte) ([]byte, bool) {
100         i := bytes.IndexAny(in, "#;")
101         if i == -1 {
102                 return nil, false
103         }
104         return in[i:], true
105 }
106
107 func readKeyName(delimiters string, in []byte) (string, int, error) {
108         line := string(in)
109
110         // Check if key name surrounded by quotes.
111         var keyQuote string
112         if line[0] == '"' {
113                 if len(line) > 6 && string(line[0:3]) == `"""` {
114                         keyQuote = `"""`
115                 } else {
116                         keyQuote = `"`
117                 }
118         } else if line[0] == '`' {
119                 keyQuote = "`"
120         }
121
122         // Get out key name
123         endIdx := -1
124         if len(keyQuote) > 0 {
125                 startIdx := len(keyQuote)
126                 // FIXME: fail case -> """"""name"""=value
127                 pos := strings.Index(line[startIdx:], keyQuote)
128                 if pos == -1 {
129                         return "", -1, fmt.Errorf("missing closing key quote: %s", line)
130                 }
131                 pos += startIdx
132
133                 // Find key-value delimiter
134                 i := strings.IndexAny(line[pos+startIdx:], delimiters)
135                 if i < 0 {
136                         return "", -1, ErrDelimiterNotFound{line}
137                 }
138                 endIdx = pos + i
139                 return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
140         }
141
142         endIdx = strings.IndexAny(line, delimiters)
143         if endIdx < 0 {
144                 return "", -1, ErrDelimiterNotFound{line}
145         }
146         return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
147 }
148
149 func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
150         for {
151                 data, err := p.readUntil('\n')
152                 if err != nil {
153                         return "", err
154                 }
155                 next := string(data)
156
157                 pos := strings.LastIndex(next, valQuote)
158                 if pos > -1 {
159                         val += next[:pos]
160
161                         comment, has := cleanComment([]byte(next[pos:]))
162                         if has {
163                                 p.comment.Write(bytes.TrimSpace(comment))
164                         }
165                         break
166                 }
167                 val += next
168                 if p.isEOF {
169                         return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
170                 }
171         }
172         return val, nil
173 }
174
175 func (p *parser) readContinuationLines(val string) (string, error) {
176         for {
177                 data, err := p.readUntil('\n')
178                 if err != nil {
179                         return "", err
180                 }
181                 next := strings.TrimSpace(string(data))
182
183                 if len(next) == 0 {
184                         break
185                 }
186                 val += next
187                 if val[len(val)-1] != '\\' {
188                         break
189                 }
190                 val = val[:len(val)-1]
191         }
192         return val, nil
193 }
194
195 // hasSurroundedQuote check if and only if the first and last characters
196 // are quotes \" or \'.
197 // It returns false if any other parts also contain same kind of quotes.
198 func hasSurroundedQuote(in string, quote byte) bool {
199         return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
200                 strings.IndexByte(in[1:], quote) == len(in)-2
201 }
202
203 func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
204
205         line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
206         if len(line) == 0 {
207                 if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
208                         return p.readPythonMultilines(line, bufferSize)
209                 }
210                 return "", nil
211         }
212
213         var valQuote string
214         if len(line) > 3 && string(line[0:3]) == `"""` {
215                 valQuote = `"""`
216         } else if line[0] == '`' {
217                 valQuote = "`"
218         } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
219                 valQuote = `"`
220         }
221
222         if len(valQuote) > 0 {
223                 startIdx := len(valQuote)
224                 pos := strings.LastIndex(line[startIdx:], valQuote)
225                 // Check for multi-line value
226                 if pos == -1 {
227                         return p.readMultilines(line, line[startIdx:], valQuote)
228                 }
229
230                 if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
231                         return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
232                 }
233                 return line[startIdx : pos+startIdx], nil
234         }
235
236         lastChar := line[len(line)-1]
237         // Won't be able to reach here if value only contains whitespace
238         line = strings.TrimSpace(line)
239         trimmedLastChar := line[len(line)-1]
240
241         // Check continuation lines when desired
242         if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
243                 return p.readContinuationLines(line[:len(line)-1])
244         }
245
246         // Check if ignore inline comment
247         if !p.options.IgnoreInlineComment {
248                 var i int
249                 if p.options.SpaceBeforeInlineComment {
250                         i = strings.Index(line, " #")
251                         if i == -1 {
252                                 i = strings.Index(line, " ;")
253                         }
254
255                 } else {
256                         i = strings.IndexAny(line, "#;")
257                 }
258
259                 if i > -1 {
260                         p.comment.WriteString(line[i:])
261                         line = strings.TrimSpace(line[:i])
262                 }
263
264         }
265
266         // Trim single and double quotes
267         if (hasSurroundedQuote(line, '\'') ||
268                 hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
269                 line = line[1 : len(line)-1]
270         } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
271                 if strings.Contains(line, `\;`) {
272                         line = strings.Replace(line, `\;`, ";", -1)
273                 }
274                 if strings.Contains(line, `\#`) {
275                         line = strings.Replace(line, `\#`, "#", -1)
276                 }
277         } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
278                 return p.readPythonMultilines(line, bufferSize)
279         }
280
281         return line, nil
282 }
283
284 func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
285         parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
286         peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
287
288         for {
289                 peekData, peekErr := peekBuffer.ReadBytes('\n')
290                 if peekErr != nil {
291                         if peekErr == io.EOF {
292                                 return line, nil
293                         }
294                         return "", peekErr
295                 }
296
297                 peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
298                 if len(peekMatches) != 3 {
299                         return line, nil
300                 }
301
302                 // NOTE: Return if not a python-ini multi-line value.
303                 currentIdentSize := len(peekMatches[1])
304                 if currentIdentSize <= 0 {
305                         return line, nil
306                 }
307
308                 // NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
309                 _, err := p.readUntil('\n')
310                 if err != nil {
311                         return "", err
312                 }
313
314                 line += fmt.Sprintf("\n%s", peekMatches[2])
315         }
316 }
317
318 // parse parses data through an io.Reader.
319 func (f *File) parse(reader io.Reader) (err error) {
320         p := newParser(reader, parserOptions{
321                 IgnoreContinuation:          f.options.IgnoreContinuation,
322                 IgnoreInlineComment:         f.options.IgnoreInlineComment,
323                 AllowPythonMultilineValues:  f.options.AllowPythonMultilineValues,
324                 SpaceBeforeInlineComment:    f.options.SpaceBeforeInlineComment,
325                 UnescapeValueDoubleQuotes:   f.options.UnescapeValueDoubleQuotes,
326                 UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
327                 PreserveSurroundedQuote:     f.options.PreserveSurroundedQuote,
328         })
329         if err = p.BOM(); err != nil {
330                 return fmt.Errorf("BOM: %v", err)
331         }
332
333         // Ignore error because default section name is never empty string.
334         name := DefaultSection
335         if f.options.Insensitive {
336                 name = strings.ToLower(DefaultSection)
337         }
338         section, _ := f.NewSection(name)
339
340         // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
341         var isLastValueEmpty bool
342         var lastRegularKey *Key
343
344         var line []byte
345         var inUnparseableSection bool
346
347         // NOTE: Iterate and increase `currentPeekSize` until
348         // the size of the parser buffer is found.
349         // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
350         parserBufferSize := 0
351         // NOTE: Peek 1kb at a time.
352         currentPeekSize := 1024
353
354         if f.options.AllowPythonMultilineValues {
355                 for {
356                         peekBytes, _ := p.buf.Peek(currentPeekSize)
357                         peekBytesLength := len(peekBytes)
358
359                         if parserBufferSize >= peekBytesLength {
360                                 break
361                         }
362
363                         currentPeekSize *= 2
364                         parserBufferSize = peekBytesLength
365                 }
366         }
367
368         for !p.isEOF {
369                 line, err = p.readUntil('\n')
370                 if err != nil {
371                         return err
372                 }
373
374                 if f.options.AllowNestedValues &&
375                         isLastValueEmpty && len(line) > 0 {
376                         if line[0] == ' ' || line[0] == '\t' {
377                                 lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
378                                 continue
379                         }
380                 }
381
382                 line = bytes.TrimLeftFunc(line, unicode.IsSpace)
383                 if len(line) == 0 {
384                         continue
385                 }
386
387                 // Comments
388                 if line[0] == '#' || line[0] == ';' {
389                         // Note: we do not care ending line break,
390                         // it is needed for adding second line,
391                         // so just clean it once at the end when set to value.
392                         p.comment.Write(line)
393                         continue
394                 }
395
396                 // Section
397                 if line[0] == '[' {
398                         // Read to the next ']' (TODO: support quoted strings)
399                         closeIdx := bytes.LastIndexByte(line, ']')
400                         if closeIdx == -1 {
401                                 return fmt.Errorf("unclosed section: %s", line)
402                         }
403
404                         name := string(line[1:closeIdx])
405                         section, err = f.NewSection(name)
406                         if err != nil {
407                                 return err
408                         }
409
410                         comment, has := cleanComment(line[closeIdx+1:])
411                         if has {
412                                 p.comment.Write(comment)
413                         }
414
415                         section.Comment = strings.TrimSpace(p.comment.String())
416
417                         // Reset aotu-counter and comments
418                         p.comment.Reset()
419                         p.count = 1
420
421                         inUnparseableSection = false
422                         for i := range f.options.UnparseableSections {
423                                 if f.options.UnparseableSections[i] == name ||
424                                         (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
425                                         inUnparseableSection = true
426                                         continue
427                                 }
428                         }
429                         continue
430                 }
431
432                 if inUnparseableSection {
433                         section.isRawSection = true
434                         section.rawBody += string(line)
435                         continue
436                 }
437
438                 kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
439                 if err != nil {
440                         // Treat as boolean key when desired, and whole line is key name.
441                         if IsErrDelimiterNotFound(err) {
442                                 switch {
443                                 case f.options.AllowBooleanKeys:
444                                         kname, err := p.readValue(line, parserBufferSize)
445                                         if err != nil {
446                                                 return err
447                                         }
448                                         key, err := section.NewBooleanKey(kname)
449                                         if err != nil {
450                                                 return err
451                                         }
452                                         key.Comment = strings.TrimSpace(p.comment.String())
453                                         p.comment.Reset()
454                                         continue
455
456                                 case f.options.SkipUnrecognizableLines:
457                                         continue
458                                 }
459                         }
460                         return err
461                 }
462
463                 // Auto increment.
464                 isAutoIncr := false
465                 if kname == "-" {
466                         isAutoIncr = true
467                         kname = "#" + strconv.Itoa(p.count)
468                         p.count++
469                 }
470
471                 value, err := p.readValue(line[offset:], parserBufferSize)
472                 if err != nil {
473                         return err
474                 }
475                 isLastValueEmpty = len(value) == 0
476
477                 key, err := section.NewKey(kname, value)
478                 if err != nil {
479                         return err
480                 }
481                 key.isAutoIncrement = isAutoIncr
482                 key.Comment = strings.TrimSpace(p.comment.String())
483                 p.comment.Reset()
484                 lastRegularKey = key
485         }
486         return nil
487 }