aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/gopkg.in/ini.v1/parser.go
diff options
context:
space:
mode:
authorLibravatar Rutger Broekhoff2023-12-29 21:31:53 +0100
committerLibravatar Rutger Broekhoff2023-12-29 21:31:53 +0100
commit404aeae4545d2426c089a5f8d5e82dae56f5212b (patch)
tree2d84e00af272b39fc04f3795ae06bc48970e57b5 /vendor/gopkg.in/ini.v1/parser.go
parent209d8b0187ed025dec9ac149ebcced3462877bff (diff)
downloadgitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.tar.gz
gitolfs3-404aeae4545d2426c089a5f8d5e82dae56f5212b.zip
Make Nix builds work
Diffstat (limited to 'vendor/gopkg.in/ini.v1/parser.go')
-rw-r--r--vendor/gopkg.in/ini.v1/parser.go520
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
15package ini
16
17import (
18 "bufio"
19 "bytes"
20 "fmt"
21 "io"
22 "regexp"
23 "strconv"
24 "strings"
25 "unicode"
26)
27
28const minReaderBufferSize = 4096
29
30var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
31
32type 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
44type parser struct {
45 buf *bufio.Reader
46 options parserOptions
47
48 isEOF bool
49 count int
50 comment *bytes.Buffer
51}
52
53func (p *parser) debug(format string, args ...interface{}) {
54 if p.options.DebugFunc != nil {
55 p.options.DebugFunc(fmt.Sprintf(format, args...))
56 }
57}
58
59func 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
75func (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
108func (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
120func 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
128func 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
174func (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
200func (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.
223func 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
228func (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
305func (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.
342func (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}