~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/ini.v1/parser.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 BOM-UTF8 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(3)
 
55
        if err != nil && err != io.EOF {
 
56
                return err
 
57
        } else if len(mask) < 3 {
 
58
                return nil
 
59
        } else if mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
 
60
                p.buf.Read(mask)
 
61
        }
 
62
        return nil
 
63
}
 
64
 
 
65
func (p *parser) readUntil(delim byte) ([]byte, error) {
 
66
        data, err := p.buf.ReadBytes(delim)
 
67
        if err != nil {
 
68
                if err == io.EOF {
 
69
                        p.isEOF = true
 
70
                } else {
 
71
                        return nil, err
 
72
                }
 
73
        }
 
74
        return data, nil
 
75
}
 
76
 
 
77
func cleanComment(in []byte) ([]byte, bool) {
 
78
        i := bytes.IndexAny(in, "#;")
 
79
        if i == -1 {
 
80
                return nil, false
 
81
        }
 
82
        return in[i:], true
 
83
}
 
84
 
 
85
func readKeyName(in []byte) (string, int, error) {
 
86
        line := string(in)
 
87
 
 
88
        // Check if key name surrounded by quotes.
 
89
        var keyQuote string
 
90
        if line[0] == '"' {
 
91
                if len(line) > 6 && string(line[0:3]) == `"""` {
 
92
                        keyQuote = `"""`
 
93
                } else {
 
94
                        keyQuote = `"`
 
95
                }
 
96
        } else if line[0] == '`' {
 
97
                keyQuote = "`"
 
98
        }
 
99
 
 
100
        // Get out key name
 
101
        endIdx := -1
 
102
        if len(keyQuote) > 0 {
 
103
                startIdx := len(keyQuote)
 
104
                // FIXME: fail case -> """"""name"""=value
 
105
                pos := strings.Index(line[startIdx:], keyQuote)
 
106
                if pos == -1 {
 
107
                        return "", -1, fmt.Errorf("missing closing key quote: %s", line)
 
108
                }
 
109
                pos += startIdx
 
110
 
 
111
                // Find key-value delimiter
 
112
                i := strings.IndexAny(line[pos+startIdx:], "=:")
 
113
                if i < 0 {
 
114
                        return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
 
115
                }
 
116
                endIdx = pos + i
 
117
                return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
 
118
        }
 
119
 
 
120
        endIdx = strings.IndexAny(line, "=:")
 
121
        if endIdx < 0 {
 
122
                return "", -1, fmt.Errorf("key-value delimiter not found: %s", line)
 
123
        }
 
124
        return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
 
125
}
 
126
 
 
127
func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
 
128
        for {
 
129
                data, err := p.readUntil('\n')
 
130
                if err != nil {
 
131
                        return "", err
 
132
                }
 
133
                next := string(data)
 
134
 
 
135
                pos := strings.LastIndex(next, valQuote)
 
136
                if pos > -1 {
 
137
                        val += next[:pos]
 
138
 
 
139
                        comment, has := cleanComment([]byte(next[pos:]))
 
140
                        if has {
 
141
                                p.comment.Write(bytes.TrimSpace(comment))
 
142
                        }
 
143
                        break
 
144
                }
 
145
                val += next
 
146
                if p.isEOF {
 
147
                        return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
 
148
                }
 
149
        }
 
150
        return val, nil
 
151
}
 
152
 
 
153
func (p *parser) readContinuationLines(val string) (string, error) {
 
154
        for {
 
155
                data, err := p.readUntil('\n')
 
156
                if err != nil {
 
157
                        return "", err
 
158
                }
 
159
                next := strings.TrimSpace(string(data))
 
160
 
 
161
                if len(next) == 0 {
 
162
                        break
 
163
                }
 
164
                val += next
 
165
                if val[len(val)-1] != '\\' {
 
166
                        break
 
167
                }
 
168
                val = val[:len(val)-1]
 
169
        }
 
170
        return val, nil
 
171
}
 
172
 
 
173
// hasSurroundedQuote check if and only if the first and last characters
 
174
// are quotes \" or \'.
 
175
// It returns false if any other parts also contain same kind of quotes.
 
176
func hasSurroundedQuote(in string, quote byte) bool {
 
177
        return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote &&
 
178
                strings.IndexByte(in[1:], quote) == len(in)-2
 
179
}
 
180
 
 
181
func (p *parser) readValue(in []byte) (string, error) {
 
182
        line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
 
183
        if len(line) == 0 {
 
184
                return "", nil
 
185
        }
 
186
 
 
187
        var valQuote string
 
188
        if len(line) > 3 && string(line[0:3]) == `"""` {
 
189
                valQuote = `"""`
 
190
        } else if line[0] == '`' {
 
191
                valQuote = "`"
 
192
        }
 
193
 
 
194
        if len(valQuote) > 0 {
 
195
                startIdx := len(valQuote)
 
196
                pos := strings.LastIndex(line[startIdx:], valQuote)
 
197
                // Check for multi-line value
 
198
                if pos == -1 {
 
199
                        return p.readMultilines(line, line[startIdx:], valQuote)
 
200
                }
 
201
 
 
202
                return line[startIdx : pos+startIdx], nil
 
203
        }
 
204
 
 
205
        // Won't be able to reach here if value only contains whitespace.
 
206
        line = strings.TrimSpace(line)
 
207
 
 
208
        // Check continuation lines
 
209
        if line[len(line)-1] == '\\' {
 
210
                return p.readContinuationLines(line[:len(line)-1])
 
211
        }
 
212
 
 
213
        i := strings.IndexAny(line, "#;")
 
214
        if i > -1 {
 
215
                p.comment.WriteString(line[i:])
 
216
                line = strings.TrimSpace(line[:i])
 
217
        }
 
218
 
 
219
        // Trim single quotes
 
220
        if hasSurroundedQuote(line, '\'') ||
 
221
                hasSurroundedQuote(line, '"') {
 
222
                line = line[1 : len(line)-1]
 
223
        }
 
224
        return line, nil
 
225
}
 
226
 
 
227
// parse parses data through an io.Reader.
 
228
func (f *File) parse(reader io.Reader) (err error) {
 
229
        p := newParser(reader)
 
230
        if err = p.BOM(); err != nil {
 
231
                return fmt.Errorf("BOM: %v", err)
 
232
        }
 
233
 
 
234
        // Ignore error because default section name is never empty string.
 
235
        section, _ := f.NewSection(DEFAULT_SECTION)
 
236
 
 
237
        var line []byte
 
238
        for !p.isEOF {
 
239
                line, err = p.readUntil('\n')
 
240
                if err != nil {
 
241
                        return err
 
242
                }
 
243
 
 
244
                line = bytes.TrimLeftFunc(line, unicode.IsSpace)
 
245
                if len(line) == 0 {
 
246
                        continue
 
247
                }
 
248
 
 
249
                // Comments
 
250
                if line[0] == '#' || line[0] == ';' {
 
251
                        // Note: we do not care ending line break,
 
252
                        // it is needed for adding second line,
 
253
                        // so just clean it once at the end when set to value.
 
254
                        p.comment.Write(line)
 
255
                        continue
 
256
                }
 
257
 
 
258
                // Section
 
259
                if line[0] == '[' {
 
260
                        // Read to the next ']' (TODO: support quoted strings)
 
261
                        closeIdx := bytes.IndexByte(line, ']')
 
262
                        if closeIdx == -1 {
 
263
                                return fmt.Errorf("unclosed section: %s", line)
 
264
                        }
 
265
 
 
266
                        section, err = f.NewSection(string(line[1:closeIdx]))
 
267
                        if err != nil {
 
268
                                return err
 
269
                        }
 
270
 
 
271
                        comment, has := cleanComment(line[closeIdx+1:])
 
272
                        if has {
 
273
                                p.comment.Write(comment)
 
274
                        }
 
275
 
 
276
                        section.Comment = strings.TrimSpace(p.comment.String())
 
277
 
 
278
                        // Reset aotu-counter and comments
 
279
                        p.comment.Reset()
 
280
                        p.count = 1
 
281
                        continue
 
282
                }
 
283
 
 
284
                kname, offset, err := readKeyName(line)
 
285
                if err != nil {
 
286
                        return err
 
287
                }
 
288
 
 
289
                // Auto increment.
 
290
                isAutoIncr := false
 
291
                if kname == "-" {
 
292
                        isAutoIncr = true
 
293
                        kname = "#" + strconv.Itoa(p.count)
 
294
                        p.count++
 
295
                }
 
296
 
 
297
                key, err := section.NewKey(kname, "")
 
298
                if err != nil {
 
299
                        return err
 
300
                }
 
301
                key.isAutoIncr = isAutoIncr
 
302
 
 
303
                value, err := p.readValue(line[offset:])
 
304
                if err != nil {
 
305
                        return err
 
306
                }
 
307
                key.SetValue(value)
 
308
                key.Comment = strings.TrimSpace(p.comment.String())
 
309
                p.comment.Reset()
 
310
        }
 
311
        return nil
 
312
}