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