aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/go-ini/ini
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/go-ini/ini')
-rw-r--r--vendor/github.com/go-ini/ini/LICENSE191
-rw-r--r--vendor/github.com/go-ini/ini/Makefile12
-rw-r--r--vendor/github.com/go-ini/ini/README.md734
-rw-r--r--vendor/github.com/go-ini/ini/README_ZH.md721
-rw-r--r--vendor/github.com/go-ini/ini/error.go32
-rw-r--r--vendor/github.com/go-ini/ini/ini.go535
-rw-r--r--vendor/github.com/go-ini/ini/key.go633
-rw-r--r--vendor/github.com/go-ini/ini/parser.go356
-rw-r--r--vendor/github.com/go-ini/ini/section.go221
-rw-r--r--vendor/github.com/go-ini/ini/struct.go431
10 files changed, 3866 insertions, 0 deletions
diff --git a/vendor/github.com/go-ini/ini/LICENSE b/vendor/github.com/go-ini/ini/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/LICENSE
@@ -0,0 +1,191 @@
1Apache License
2Version 2.0, January 2004
3http://www.apache.org/licenses/
4
5TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
71. Definitions.
8
9"License" shall mean the terms and conditions for use, reproduction, and
10distribution as defined by Sections 1 through 9 of this document.
11
12"Licensor" shall mean the copyright owner or entity authorized by the copyright
13owner that is granting the License.
14
15"Legal Entity" shall mean the union of the acting entity and all other entities
16that control, are controlled by, or are under common control with that entity.
17For the purposes of this definition, "control" means (i) the power, direct or
18indirect, to cause the direction or management of such entity, whether by
19contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20outstanding shares, or (iii) beneficial ownership of such entity.
21
22"You" (or "Your") shall mean an individual or Legal Entity exercising
23permissions granted by this License.
24
25"Source" form shall mean the preferred form for making modifications, including
26but not limited to software source code, documentation source, and configuration
27files.
28
29"Object" form shall mean any form resulting from mechanical transformation or
30translation of a Source form, including but not limited to compiled object code,
31generated documentation, and conversions to other media types.
32
33"Work" shall mean the work of authorship, whether in Source or Object form, made
34available under the License, as indicated by a copyright notice that is included
35in or attached to the work (an example is provided in the Appendix below).
36
37"Derivative Works" shall mean any work, whether in Source or Object form, that
38is based on (or derived from) the Work and for which the editorial revisions,
39annotations, elaborations, or other modifications represent, as a whole, an
40original work of authorship. For the purposes of this License, Derivative Works
41shall not include works that remain separable from, or merely link (or bind by
42name) to the interfaces of, the Work and Derivative Works thereof.
43
44"Contribution" shall mean any work of authorship, including the original version
45of the Work and any modifications or additions to that Work or Derivative Works
46thereof, that is intentionally submitted to Licensor for inclusion in the Work
47by the copyright owner or by an individual or Legal Entity authorized to submit
48on behalf of the copyright owner. For the purposes of this definition,
49"submitted" means any form of electronic, verbal, or written communication sent
50to the Licensor or its representatives, including but not limited to
51communication on electronic mailing lists, source code control systems, and
52issue tracking systems that are managed by, or on behalf of, the Licensor for
53the purpose of discussing and improving the Work, but excluding communication
54that is conspicuously marked or otherwise designated in writing by the copyright
55owner as "Not a Contribution."
56
57"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58of whom a Contribution has been received by Licensor and subsequently
59incorporated within the Work.
60
612. Grant of Copyright License.
62
63Subject to the terms and conditions of this License, each Contributor hereby
64grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65irrevocable copyright license to reproduce, prepare Derivative Works of,
66publicly display, publicly perform, sublicense, and distribute the Work and such
67Derivative Works in Source or Object form.
68
693. Grant of Patent License.
70
71Subject to the terms and conditions of this License, each Contributor hereby
72grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73irrevocable (except as stated in this section) patent license to make, have
74made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75such license applies only to those patent claims licensable by such Contributor
76that are necessarily infringed by their Contribution(s) alone or by combination
77of their Contribution(s) with the Work to which such Contribution(s) was
78submitted. If You institute patent litigation against any entity (including a
79cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80Contribution incorporated within the Work constitutes direct or contributory
81patent infringement, then any patent licenses granted to You under this License
82for that Work shall terminate as of the date such litigation is filed.
83
844. Redistribution.
85
86You may reproduce and distribute copies of the Work or Derivative Works thereof
87in any medium, with or without modifications, and in Source or Object form,
88provided that You meet the following conditions:
89
90You must give any other recipients of the Work or Derivative Works a copy of
91this License; and
92You must cause any modified files to carry prominent notices stating that You
93changed the files; and
94You must retain, in the Source form of any Derivative Works that You distribute,
95all copyright, patent, trademark, and attribution notices from the Source form
96of the Work, excluding those notices that do not pertain to any part of the
97Derivative Works; and
98If the Work includes a "NOTICE" text file as part of its distribution, then any
99Derivative Works that You distribute must include a readable copy of the
100attribution notices contained within such NOTICE file, excluding those notices
101that do not pertain to any part of the Derivative Works, in at least one of the
102following places: within a NOTICE text file distributed as part of the
103Derivative Works; within the Source form or documentation, if provided along
104with the Derivative Works; or, within a display generated by the Derivative
105Works, if and wherever such third-party notices normally appear. The contents of
106the NOTICE file are for informational purposes only and do not modify the
107License. You may add Your own attribution notices within Derivative Works that
108You distribute, alongside or as an addendum to the NOTICE text from the Work,
109provided that such additional attribution notices cannot be construed as
110modifying the License.
111You may add Your own copyright statement to Your modifications and may provide
112additional or different license terms and conditions for use, reproduction, or
113distribution of Your modifications, or for any such Derivative Works as a whole,
114provided Your use, reproduction, and distribution of the Work otherwise complies
115with the conditions stated in this License.
116
1175. Submission of Contributions.
118
119Unless You explicitly state otherwise, any Contribution intentionally submitted
120for inclusion in the Work by You to the Licensor shall be under the terms and
121conditions of this License, without any additional terms or conditions.
122Notwithstanding the above, nothing herein shall supersede or modify the terms of
123any separate license agreement you may have executed with Licensor regarding
124such Contributions.
125
1266. Trademarks.
127
128This License does not grant permission to use the trade names, trademarks,
129service marks, or product names of the Licensor, except as required for
130reasonable and customary use in describing the origin of the Work and
131reproducing the content of the NOTICE file.
132
1337. Disclaimer of Warranty.
134
135Unless required by applicable law or agreed to in writing, Licensor provides the
136Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138including, without limitation, any warranties or conditions of TITLE,
139NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140solely responsible for determining the appropriateness of using or
141redistributing the Work and assume any risks associated with Your exercise of
142permissions under this License.
143
1448. Limitation of Liability.
145
146In no event and under no legal theory, whether in tort (including negligence),
147contract, or otherwise, unless required by applicable law (such as deliberate
148and grossly negligent acts) or agreed to in writing, shall any Contributor be
149liable to You for damages, including any direct, indirect, special, incidental,
150or consequential damages of any character arising as a result of this License or
151out of the use or inability to use the Work (including but not limited to
152damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153any and all other commercial damages or losses), even if such Contributor has
154been advised of the possibility of such damages.
155
1569. Accepting Warranty or Additional Liability.
157
158While redistributing the Work or Derivative Works thereof, You may choose to
159offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160other liability obligations and/or rights consistent with this License. However,
161in accepting such obligations, You may act only on Your own behalf and on Your
162sole responsibility, not on behalf of any other Contributor, and only if You
163agree to indemnify, defend, and hold each Contributor harmless for any liability
164incurred by, or claims asserted against, such Contributor by reason of your
165accepting any such warranty or additional liability.
166
167END OF TERMS AND CONDITIONS
168
169APPENDIX: How to apply the Apache License to your work
170
171To apply the Apache License to your work, attach the following boilerplate
172notice, with the fields enclosed by brackets "[]" replaced with your own
173identifying information. (Don't include the brackets!) The text should be
174enclosed in the appropriate comment syntax for the file format. We also
175recommend that a file or class name and description of purpose be included on
176the same "printed page" as the copyright notice for easier identification within
177third-party archives.
178
179 Copyright [yyyy] [name of copyright owner]
180
181 Licensed under the Apache License, Version 2.0 (the "License");
182 you may not use this file except in compliance with the License.
183 You may obtain a copy of the License at
184
185 http://www.apache.org/licenses/LICENSE-2.0
186
187 Unless required by applicable law or agreed to in writing, software
188 distributed under the License is distributed on an "AS IS" BASIS,
189 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 See the License for the specific language governing permissions and
191 limitations under the License.
diff --git a/vendor/github.com/go-ini/ini/Makefile b/vendor/github.com/go-ini/ini/Makefile
new file mode 100644
index 0000000..ac034e5
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/Makefile
@@ -0,0 +1,12 @@
1.PHONY: build test bench vet
2
3build: vet bench
4
5test:
6 go test -v -cover -race
7
8bench:
9 go test -v -cover -race -test.bench=. -test.benchmem
10
11vet:
12 go vet
diff --git a/vendor/github.com/go-ini/ini/README.md b/vendor/github.com/go-ini/ini/README.md
new file mode 100644
index 0000000..22a4234
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/README.md
@@ -0,0 +1,734 @@
1INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini)
2===
3
4![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
5
6Package ini provides INI file read and write functionality in Go.
7
8[简体中文](README_ZH.md)
9
10## Feature
11
12- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
13- Read with recursion values.
14- Read with parent-child sections.
15- Read with auto-increment key names.
16- Read with multiple-line values.
17- Read with tons of helper methods.
18- Read and convert values to Go types.
19- Read and **WRITE** comments of sections and keys.
20- Manipulate sections, keys and comments with ease.
21- Keep sections and keys in order as you parse and save.
22
23## Installation
24
25To use a tagged revision:
26
27 go get gopkg.in/ini.v1
28
29To use with latest changes:
30
31 go get github.com/go-ini/ini
32
33Please add `-u` flag to update in the future.
34
35### Testing
36
37If you want to test on your machine, please apply `-t` flag:
38
39 go get -t gopkg.in/ini.v1
40
41Please add `-u` flag to update in the future.
42
43## Getting Started
44
45### Loading from data sources
46
47A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
48
49```go
50cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
51```
52
53Or start with an empty object:
54
55```go
56cfg := ini.Empty()
57```
58
59When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
60
61```go
62err := cfg.Append("other file", []byte("other raw data"))
63```
64
65If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
66
67```go
68cfg, err := ini.LooseLoad("filename", "filename_404")
69```
70
71The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
72
73#### Ignore cases of key name
74
75When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
76
77```go
78cfg, err := ini.InsensitiveLoad("filename")
79//...
80
81// sec1 and sec2 are the exactly same section object
82sec1, err := cfg.GetSection("Section")
83sec2, err := cfg.GetSection("SecTIOn")
84
85// key1 and key2 are the exactly same key object
86key1, err := cfg.GetKey("Key")
87key2, err := cfg.GetKey("KeY")
88```
89
90#### MySQL-like boolean key
91
92MySQL's configuration allows a key without value as follows:
93
94```ini
95[mysqld]
96...
97skip-host-cache
98skip-name-resolve
99```
100
101By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
102
103```go
104cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
105```
106
107The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
108
109#### Comment
110
111Take care that following format will be treated as comment:
112
1131. Line begins with `#` or `;`
1142. Words after `#` or `;`
1153. Words after section name (i.e words after `[some section name]`)
116
117If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
118
119### Working with sections
120
121To get a section, you would need to:
122
123```go
124section, err := cfg.GetSection("section name")
125```
126
127For a shortcut for default section, just give an empty string as name:
128
129```go
130section, err := cfg.GetSection("")
131```
132
133When you're pretty sure the section exists, following code could make your life easier:
134
135```go
136section := cfg.Section("section name")
137```
138
139What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
140
141To create a new section:
142
143```go
144err := cfg.NewSection("new section")
145```
146
147To get a list of sections or section names:
148
149```go
150sections := cfg.Sections()
151names := cfg.SectionStrings()
152```
153
154### Working with keys
155
156To get a key under a section:
157
158```go
159key, err := cfg.Section("").GetKey("key name")
160```
161
162Same rule applies to key operations:
163
164```go
165key := cfg.Section("").Key("key name")
166```
167
168To check if a key exists:
169
170```go
171yes := cfg.Section("").HasKey("key name")
172```
173
174To create a new key:
175
176```go
177err := cfg.Section("").NewKey("name", "value")
178```
179
180To get a list of keys or key names:
181
182```go
183keys := cfg.Section("").Keys()
184names := cfg.Section("").KeyStrings()
185```
186
187To get a clone hash of keys and corresponding values:
188
189```go
190hash := cfg.Section("").KeysHash()
191```
192
193### Working with values
194
195To get a string value:
196
197```go
198val := cfg.Section("").Key("key name").String()
199```
200
201To validate key value on the fly:
202
203```go
204val := cfg.Section("").Key("key name").Validate(func(in string) string {
205 if len(in) == 0 {
206 return "default"
207 }
208 return in
209})
210```
211
212If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
213
214```go
215val := cfg.Section("").Key("key name").Value()
216```
217
218To check if raw value exists:
219
220```go
221yes := cfg.Section("").HasValue("test value")
222```
223
224To get value with types:
225
226```go
227// For boolean values:
228// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
229// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
230v, err = cfg.Section("").Key("BOOL").Bool()
231v, err = cfg.Section("").Key("FLOAT64").Float64()
232v, err = cfg.Section("").Key("INT").Int()
233v, err = cfg.Section("").Key("INT64").Int64()
234v, err = cfg.Section("").Key("UINT").Uint()
235v, err = cfg.Section("").Key("UINT64").Uint64()
236v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
237v, err = cfg.Section("").Key("TIME").Time() // RFC3339
238
239v = cfg.Section("").Key("BOOL").MustBool()
240v = cfg.Section("").Key("FLOAT64").MustFloat64()
241v = cfg.Section("").Key("INT").MustInt()
242v = cfg.Section("").Key("INT64").MustInt64()
243v = cfg.Section("").Key("UINT").MustUint()
244v = cfg.Section("").Key("UINT64").MustUint64()
245v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
246v = cfg.Section("").Key("TIME").MustTime() // RFC3339
247
248// Methods start with Must also accept one argument for default value
249// when key not found or fail to parse value to given type.
250// Except method MustString, which you have to pass a default value.
251
252v = cfg.Section("").Key("String").MustString("default")
253v = cfg.Section("").Key("BOOL").MustBool(true)
254v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
255v = cfg.Section("").Key("INT").MustInt(10)
256v = cfg.Section("").Key("INT64").MustInt64(99)
257v = cfg.Section("").Key("UINT").MustUint(3)
258v = cfg.Section("").Key("UINT64").MustUint64(6)
259v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
260v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
261```
262
263What if my value is three-line long?
264
265```ini
266[advance]
267ADDRESS = """404 road,
268NotFound, State, 5000
269Earth"""
270```
271
272Not a problem!
273
274```go
275cfg.Section("advance").Key("ADDRESS").String()
276
277/* --- start ---
278404 road,
279NotFound, State, 5000
280Earth
281------ end --- */
282```
283
284That's cool, how about continuation lines?
285
286```ini
287[advance]
288two_lines = how about \
289 continuation lines?
290lots_of_lines = 1 \
291 2 \
292 3 \
293 4
294```
295
296Piece of cake!
297
298```go
299cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
300cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
301```
302
303Well, I hate continuation lines, how do I disable that?
304
305```go
306cfg, err := ini.LoadSources(ini.LoadOptions{
307 IgnoreContinuation: true,
308}, "filename")
309```
310
311Holy crap!
312
313Note that single quotes around values will be stripped:
314
315```ini
316foo = "some value" // foo: some value
317bar = 'some value' // bar: some value
318```
319
320That's all? Hmm, no.
321
322#### Helper methods of working with values
323
324To get value with given candidates:
325
326```go
327v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
328v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
329v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
330v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
331v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
332v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
333v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
334v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
335```
336
337Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
338
339To validate value in a given range:
340
341```go
342vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
343vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
344vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
345vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
346vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
347vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
348vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
349```
350
351##### Auto-split values into a slice
352
353To use zero value of type for invalid inputs:
354
355```go
356// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
357// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
358vals = cfg.Section("").Key("STRINGS").Strings(",")
359vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
360vals = cfg.Section("").Key("INTS").Ints(",")
361vals = cfg.Section("").Key("INT64S").Int64s(",")
362vals = cfg.Section("").Key("UINTS").Uints(",")
363vals = cfg.Section("").Key("UINT64S").Uint64s(",")
364vals = cfg.Section("").Key("TIMES").Times(",")
365```
366
367To exclude invalid values out of result slice:
368
369```go
370// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
371// Input: how, 2.2, are, you -> [2.2]
372vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
373vals = cfg.Section("").Key("INTS").ValidInts(",")
374vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
375vals = cfg.Section("").Key("UINTS").ValidUints(",")
376vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
377vals = cfg.Section("").Key("TIMES").ValidTimes(",")
378```
379
380Or to return nothing but error when have invalid inputs:
381
382```go
383// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
384// Input: how, 2.2, are, you -> error
385vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
386vals = cfg.Section("").Key("INTS").StrictInts(",")
387vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
388vals = cfg.Section("").Key("UINTS").StrictUints(",")
389vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
390vals = cfg.Section("").Key("TIMES").StrictTimes(",")
391```
392
393### Save your configuration
394
395Finally, it's time to save your configuration to somewhere.
396
397A typical way to save configuration is writing it to a file:
398
399```go
400// ...
401err = cfg.SaveTo("my.ini")
402err = cfg.SaveToIndent("my.ini", "\t")
403```
404
405Another way to save is writing to a `io.Writer` interface:
406
407```go
408// ...
409cfg.WriteTo(writer)
410cfg.WriteToIndent(writer, "\t")
411```
412
413By default, spaces are used to align "=" sign between key and values, to disable that:
414
415```go
416ini.PrettyFormat = false
417```
418
419## Advanced Usage
420
421### Recursive Values
422
423For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
424
425```ini
426NAME = ini
427
428[author]
429NAME = Unknwon
430GITHUB = https://github.com/%(NAME)s
431
432[package]
433FULL_NAME = github.com/go-ini/%(NAME)s
434```
435
436```go
437cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
438cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
439```
440
441### Parent-child Sections
442
443You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
444
445```ini
446NAME = ini
447VERSION = v1
448IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
449
450[package]
451CLONE_URL = https://%(IMPORT_PATH)s
452
453[package.sub]
454```
455
456```go
457cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
458```
459
460#### Retrieve parent keys available to a child section
461
462```go
463cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
464```
465
466### Unparseable Sections
467
468Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
469
470```go
471cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
472<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
473
474body := cfg.Section("COMMENTS").Body()
475
476/* --- start ---
477<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
478------ end --- */
479```
480
481### Auto-increment Key Names
482
483If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
484
485```ini
486[features]
487-: Support read/write comments of keys and sections
488-: Support auto-increment of key names
489-: Support load multiple files to overwrite key values
490```
491
492```go
493cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
494```
495
496### Map To Struct
497
498Want more objective way to play with INI? Cool.
499
500```ini
501Name = Unknwon
502age = 21
503Male = true
504Born = 1993-01-01T20:17:05Z
505
506[Note]
507Content = Hi is a good man!
508Cities = HangZhou, Boston
509```
510
511```go
512type Note struct {
513 Content string
514 Cities []string
515}
516
517type Person struct {
518 Name string
519 Age int `ini:"age"`
520 Male bool
521 Born time.Time
522 Note
523 Created time.Time `ini:"-"`
524}
525
526func main() {
527 cfg, err := ini.Load("path/to/ini")
528 // ...
529 p := new(Person)
530 err = cfg.MapTo(p)
531 // ...
532
533 // Things can be simpler.
534 err = ini.MapTo(p, "path/to/ini")
535 // ...
536
537 // Just map a section? Fine.
538 n := new(Note)
539 err = cfg.Section("Note").MapTo(n)
540 // ...
541}
542```
543
544Can I have default value for field? Absolutely.
545
546Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
547
548```go
549// ...
550p := &Person{
551 Name: "Joe",
552}
553// ...
554```
555
556It's really cool, but what's the point if you can't give me my file back from struct?
557
558### Reflect From Struct
559
560Why not?
561
562```go
563type Embeded struct {
564 Dates []time.Time `delim:"|"`
565 Places []string `ini:"places,omitempty"`
566 None []int `ini:",omitempty"`
567}
568
569type Author struct {
570 Name string `ini:"NAME"`
571 Male bool
572 Age int
573 GPA float64
574 NeverMind string `ini:"-"`
575 *Embeded
576}
577
578func main() {
579 a := &Author{"Unknwon", true, 21, 2.8, "",
580 &Embeded{
581 []time.Time{time.Now(), time.Now()},
582 []string{"HangZhou", "Boston"},
583 []int{},
584 }}
585 cfg := ini.Empty()
586 err = ini.ReflectFrom(cfg, a)
587 // ...
588}
589```
590
591So, what do I get?
592
593```ini
594NAME = Unknwon
595Male = true
596Age = 21
597GPA = 2.8
598
599[Embeded]
600Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
601places = HangZhou,Boston
602```
603
604#### Name Mapper
605
606To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
607
608There are 2 built-in name mappers:
609
610- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
611- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
612
613To use them:
614
615```go
616type Info struct {
617 PackageName string
618}
619
620func main() {
621 err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
622 // ...
623
624 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
625 // ...
626 info := new(Info)
627 cfg.NameMapper = ini.AllCapsUnderscore
628 err = cfg.MapTo(info)
629 // ...
630}
631```
632
633Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
634
635#### Value Mapper
636
637To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
638
639```go
640type Env struct {
641 Foo string `ini:"foo"`
642}
643
644func main() {
645 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
646 cfg.ValueMapper = os.ExpandEnv
647 // ...
648 env := &Env{}
649 err = cfg.Section("env").MapTo(env)
650}
651```
652
653This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
654
655#### Other Notes On Map/Reflect
656
657Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
658
659```go
660type Child struct {
661 Age string
662}
663
664type Parent struct {
665 Name string
666 Child
667}
668
669type Config struct {
670 City string
671 Parent
672}
673```
674
675Example configuration:
676
677```ini
678City = Boston
679
680[Parent]
681Name = Unknwon
682
683[Child]
684Age = 21
685```
686
687What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
688
689```go
690type Child struct {
691 Age string
692}
693
694type Parent struct {
695 Name string
696 Child `ini:"Parent"`
697}
698
699type Config struct {
700 City string
701 Parent
702}
703```
704
705Example configuration:
706
707```ini
708City = Boston
709
710[Parent]
711Name = Unknwon
712Age = 21
713```
714
715## Getting Help
716
717- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
718- [File An Issue](https://github.com/go-ini/ini/issues/new)
719
720## FAQs
721
722### What does `BlockMode` field do?
723
724By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
725
726### Why another INI library?
727
728Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
729
730To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
731
732## License
733
734This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/vendor/github.com/go-ini/ini/README_ZH.md b/vendor/github.com/go-ini/ini/README_ZH.md
new file mode 100644
index 0000000..3b4fb66
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/README_ZH.md
@@ -0,0 +1,721 @@
1本包提供了 Go 语言中读写 INI 文件的功能。
2
3## 功能特性
4
5- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
6- 支持递归读取键值
7- 支持读取父子分区
8- 支持读取自增键名
9- 支持读取多行的键值
10- 支持大量辅助方法
11- 支持在读取时直接转换为 Go 语言类型
12- 支持读取和 **写入** 分区和键的注释
13- 轻松操作分区、键值和注释
14- 在保存文件时分区和键值会保持原有的顺序
15
16## 下载安装
17
18使用一个特定版本:
19
20 go get gopkg.in/ini.v1
21
22使用最新版:
23
24 go get github.com/go-ini/ini
25
26如需更新请添加 `-u` 选项。
27
28### 测试安装
29
30如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
31
32 go get -t gopkg.in/ini.v1
33
34如需更新请添加 `-u` 选项。
35
36## 开始使用
37
38### 从数据源加载
39
40一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
41
42```go
43cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
44```
45
46或者从一个空白的文件开始:
47
48```go
49cfg := ini.Empty()
50```
51
52当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
53
54```go
55err := cfg.Append("other file", []byte("other raw data"))
56```
57
58当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
59
60```go
61cfg, err := ini.LooseLoad("filename", "filename_404")
62```
63
64更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
65
66#### 忽略键名的大小写
67
68有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
69
70```go
71cfg, err := ini.InsensitiveLoad("filename")
72//...
73
74// sec1 和 sec2 指向同一个分区对象
75sec1, err := cfg.GetSection("Section")
76sec2, err := cfg.GetSection("SecTIOn")
77
78// key1 和 key2 指向同一个键对象
79key1, err := cfg.GetKey("Key")
80key2, err := cfg.GetKey("KeY")
81```
82
83#### 类似 MySQL 配置中的布尔值键
84
85MySQL 的配置文件中会出现没有具体值的布尔类型的键:
86
87```ini
88[mysqld]
89...
90skip-host-cache
91skip-name-resolve
92```
93
94默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
95
96```go
97cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
98```
99
100这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
101
102#### 关于注释
103
104下述几种情况的内容将被视为注释:
105
1061. 所有以 `#` 或 `;` 开头的行
1072. 所有在 `#` 或 `;` 之后的内容
1083. 分区标签后的文字 (即 `[分区名]` 之后的内容)
109
110如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
111
112### 操作分区(Section)
113
114获取指定分区:
115
116```go
117section, err := cfg.GetSection("section name")
118```
119
120如果您想要获取默认分区,则可以用空字符串代替分区名:
121
122```go
123section, err := cfg.GetSection("")
124```
125
126当您非常确定某个分区是存在的,可以使用以下简便方法:
127
128```go
129section := cfg.Section("section name")
130```
131
132如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
133
134创建一个分区:
135
136```go
137err := cfg.NewSection("new section")
138```
139
140获取所有分区对象或名称:
141
142```go
143sections := cfg.Sections()
144names := cfg.SectionStrings()
145```
146
147### 操作键(Key)
148
149获取某个分区下的键:
150
151```go
152key, err := cfg.Section("").GetKey("key name")
153```
154
155和分区一样,您也可以直接获取键而忽略错误处理:
156
157```go
158key := cfg.Section("").Key("key name")
159```
160
161判断某个键是否存在:
162
163```go
164yes := cfg.Section("").HasKey("key name")
165```
166
167创建一个新的键:
168
169```go
170err := cfg.Section("").NewKey("name", "value")
171```
172
173获取分区下的所有键或键名:
174
175```go
176keys := cfg.Section("").Keys()
177names := cfg.Section("").KeyStrings()
178```
179
180获取分区下的所有键值对的克隆:
181
182```go
183hash := cfg.Section("").KeysHash()
184```
185
186### 操作键值(Value)
187
188获取一个类型为字符串(string)的值:
189
190```go
191val := cfg.Section("").Key("key name").String()
192```
193
194获取值的同时通过自定义函数进行处理验证:
195
196```go
197val := cfg.Section("").Key("key name").Validate(func(in string) string {
198 if len(in) == 0 {
199 return "default"
200 }
201 return in
202})
203```
204
205如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
206
207```go
208val := cfg.Section("").Key("key name").Value()
209```
210
211判断某个原值是否存在:
212
213```go
214yes := cfg.Section("").HasValue("test value")
215```
216
217获取其它类型的值:
218
219```go
220// 布尔值的规则:
221// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
222// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
223v, err = cfg.Section("").Key("BOOL").Bool()
224v, err = cfg.Section("").Key("FLOAT64").Float64()
225v, err = cfg.Section("").Key("INT").Int()
226v, err = cfg.Section("").Key("INT64").Int64()
227v, err = cfg.Section("").Key("UINT").Uint()
228v, err = cfg.Section("").Key("UINT64").Uint64()
229v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
230v, err = cfg.Section("").Key("TIME").Time() // RFC3339
231
232v = cfg.Section("").Key("BOOL").MustBool()
233v = cfg.Section("").Key("FLOAT64").MustFloat64()
234v = cfg.Section("").Key("INT").MustInt()
235v = cfg.Section("").Key("INT64").MustInt64()
236v = cfg.Section("").Key("UINT").MustUint()
237v = cfg.Section("").Key("UINT64").MustUint64()
238v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
239v = cfg.Section("").Key("TIME").MustTime() // RFC3339
240
241// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
242// 当键不存在或者转换失败时,则会直接返回该默认值。
243// 但是,MustString 方法必须传递一个默认值。
244
245v = cfg.Seciont("").Key("String").MustString("default")
246v = cfg.Section("").Key("BOOL").MustBool(true)
247v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
248v = cfg.Section("").Key("INT").MustInt(10)
249v = cfg.Section("").Key("INT64").MustInt64(99)
250v = cfg.Section("").Key("UINT").MustUint(3)
251v = cfg.Section("").Key("UINT64").MustUint64(6)
252v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
253v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
254```
255
256如果我的值有好多行怎么办?
257
258```ini
259[advance]
260ADDRESS = """404 road,
261NotFound, State, 5000
262Earth"""
263```
264
265嗯哼?小 case!
266
267```go
268cfg.Section("advance").Key("ADDRESS").String()
269
270/* --- start ---
271404 road,
272NotFound, State, 5000
273Earth
274------ end --- */
275```
276
277赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
278
279```ini
280[advance]
281two_lines = how about \
282 continuation lines?
283lots_of_lines = 1 \
284 2 \
285 3 \
286 4
287```
288
289简直是小菜一碟!
290
291```go
292cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
293cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
294```
295
296可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
297
298```go
299cfg, err := ini.LoadSources(ini.LoadOptions{
300 IgnoreContinuation: true,
301}, "filename")
302```
303
304哇靠给力啊!
305
306需要注意的是,值两侧的单引号会被自动剔除:
307
308```ini
309foo = "some value" // foo: some value
310bar = 'some value' // bar: some value
311```
312
313这就是全部了?哈哈,当然不是。
314
315#### 操作键值的辅助方法
316
317获取键值时设定候选值:
318
319```go
320v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
321v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
322v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
323v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
324v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
325v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
326v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
327v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
328```
329
330如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
331
332验证获取的值是否在指定范围内:
333
334```go
335vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
336vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
337vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
338vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
339vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
340vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
341vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
342```
343
344##### 自动分割键值到切片(slice)
345
346当存在无效输入时,使用零值代替:
347
348```go
349// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
350// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
351vals = cfg.Section("").Key("STRINGS").Strings(",")
352vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
353vals = cfg.Section("").Key("INTS").Ints(",")
354vals = cfg.Section("").Key("INT64S").Int64s(",")
355vals = cfg.Section("").Key("UINTS").Uints(",")
356vals = cfg.Section("").Key("UINT64S").Uint64s(",")
357vals = cfg.Section("").Key("TIMES").Times(",")
358```
359
360从结果切片中剔除无效输入:
361
362```go
363// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
364// Input: how, 2.2, are, you -> [2.2]
365vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
366vals = cfg.Section("").Key("INTS").ValidInts(",")
367vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
368vals = cfg.Section("").Key("UINTS").ValidUints(",")
369vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
370vals = cfg.Section("").Key("TIMES").ValidTimes(",")
371```
372
373当存在无效输入时,直接返回错误:
374
375```go
376// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
377// Input: how, 2.2, are, you -> error
378vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
379vals = cfg.Section("").Key("INTS").StrictInts(",")
380vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
381vals = cfg.Section("").Key("UINTS").StrictUints(",")
382vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
383vals = cfg.Section("").Key("TIMES").StrictTimes(",")
384```
385
386### 保存配置
387
388终于到了这个时刻,是时候保存一下配置了。
389
390比较原始的做法是输出配置到某个文件:
391
392```go
393// ...
394err = cfg.SaveTo("my.ini")
395err = cfg.SaveToIndent("my.ini", "\t")
396```
397
398另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
399
400```go
401// ...
402cfg.WriteTo(writer)
403cfg.WriteToIndent(writer, "\t")
404```
405
406默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
407
408```go
409ini.PrettyFormat = false
410```
411
412## 高级用法
413
414### 递归读取键值
415
416在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
417
418```ini
419NAME = ini
420
421[author]
422NAME = Unknwon
423GITHUB = https://github.com/%(NAME)s
424
425[package]
426FULL_NAME = github.com/go-ini/%(NAME)s
427```
428
429```go
430cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
431cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
432```
433
434### 读取父子分区
435
436您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
437
438```ini
439NAME = ini
440VERSION = v1
441IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
442
443[package]
444CLONE_URL = https://%(IMPORT_PATH)s
445
446[package.sub]
447```
448
449```go
450cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
451```
452
453#### 获取上级父分区下的所有键名
454
455```go
456cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
457```
458
459### 无法解析的分区
460
461如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
462
463```go
464cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
465<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
466
467body := cfg.Section("COMMENTS").Body()
468
469/* --- start ---
470<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
471------ end --- */
472```
473
474### 读取自增键名
475
476如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
477
478```ini
479[features]
480-: Support read/write comments of keys and sections
481-: Support auto-increment of key names
482-: Support load multiple files to overwrite key values
483```
484
485```go
486cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
487```
488
489### 映射到结构
490
491想要使用更加面向对象的方式玩转 INI 吗?好主意。
492
493```ini
494Name = Unknwon
495age = 21
496Male = true
497Born = 1993-01-01T20:17:05Z
498
499[Note]
500Content = Hi is a good man!
501Cities = HangZhou, Boston
502```
503
504```go
505type Note struct {
506 Content string
507 Cities []string
508}
509
510type Person struct {
511 Name string
512 Age int `ini:"age"`
513 Male bool
514 Born time.Time
515 Note
516 Created time.Time `ini:"-"`
517}
518
519func main() {
520 cfg, err := ini.Load("path/to/ini")
521 // ...
522 p := new(Person)
523 err = cfg.MapTo(p)
524 // ...
525
526 // 一切竟可以如此的简单。
527 err = ini.MapTo(p, "path/to/ini")
528 // ...
529
530 // 嗯哼?只需要映射一个分区吗?
531 n := new(Note)
532 err = cfg.Section("Note").MapTo(n)
533 // ...
534}
535```
536
537结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
538
539```go
540// ...
541p := &Person{
542 Name: "Joe",
543}
544// ...
545```
546
547这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
548
549### 从结构反射
550
551可是,我有说不能吗?
552
553```go
554type Embeded struct {
555 Dates []time.Time `delim:"|"`
556 Places []string `ini:"places,omitempty"`
557 None []int `ini:",omitempty"`
558}
559
560type Author struct {
561 Name string `ini:"NAME"`
562 Male bool
563 Age int
564 GPA float64
565 NeverMind string `ini:"-"`
566 *Embeded
567}
568
569func main() {
570 a := &Author{"Unknwon", true, 21, 2.8, "",
571 &Embeded{
572 []time.Time{time.Now(), time.Now()},
573 []string{"HangZhou", "Boston"},
574 []int{},
575 }}
576 cfg := ini.Empty()
577 err = ini.ReflectFrom(cfg, a)
578 // ...
579}
580```
581
582瞧瞧,奇迹发生了。
583
584```ini
585NAME = Unknwon
586Male = true
587Age = 21
588GPA = 2.8
589
590[Embeded]
591Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
592places = HangZhou,Boston
593```
594
595#### 名称映射器(Name Mapper)
596
597为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
598
599目前有 2 款内置的映射器:
600
601- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
602- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
603
604使用方法:
605
606```go
607type Info struct{
608 PackageName string
609}
610
611func main() {
612 err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
613 // ...
614
615 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
616 // ...
617 info := new(Info)
618 cfg.NameMapper = ini.AllCapsUnderscore
619 err = cfg.MapTo(info)
620 // ...
621}
622```
623
624使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
625
626#### 值映射器(Value Mapper)
627
628值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
629
630```go
631type Env struct {
632 Foo string `ini:"foo"`
633}
634
635func main() {
636 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
637 cfg.ValueMapper = os.ExpandEnv
638 // ...
639 env := &Env{}
640 err = cfg.Section("env").MapTo(env)
641}
642```
643
644本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
645
646#### 映射/反射的其它说明
647
648任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
649
650```go
651type Child struct {
652 Age string
653}
654
655type Parent struct {
656 Name string
657 Child
658}
659
660type Config struct {
661 City string
662 Parent
663}
664```
665
666示例配置文件:
667
668```ini
669City = Boston
670
671[Parent]
672Name = Unknwon
673
674[Child]
675Age = 21
676```
677
678很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
679
680```go
681type Child struct {
682 Age string
683}
684
685type Parent struct {
686 Name string
687 Child `ini:"Parent"`
688}
689
690type Config struct {
691 City string
692 Parent
693}
694```
695
696示例配置文件:
697
698```ini
699City = Boston
700
701[Parent]
702Name = Unknwon
703Age = 21
704```
705
706## 获取帮助
707
708- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
709- [创建工单](https://github.com/go-ini/ini/issues/new)
710
711## 常见问题
712
713### 字段 `BlockMode` 是什么?
714
715默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
716
717### 为什么要写另一个 INI 解析库?
718
719许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
720
721为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
diff --git a/vendor/github.com/go-ini/ini/error.go b/vendor/github.com/go-ini/ini/error.go
new file mode 100644
index 0000000..80afe74
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/error.go
@@ -0,0 +1,32 @@
1// Copyright 2016 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 "fmt"
19)
20
21type ErrDelimiterNotFound struct {
22 Line string
23}
24
25func IsErrDelimiterNotFound(err error) bool {
26 _, ok := err.(ErrDelimiterNotFound)
27 return ok
28}
29
30func (err ErrDelimiterNotFound) Error() string {
31 return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
32}
diff --git a/vendor/github.com/go-ini/ini/ini.go b/vendor/github.com/go-ini/ini/ini.go
new file mode 100644
index 0000000..77e0dbd
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/ini.go
@@ -0,0 +1,535 @@
1// Copyright 2014 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 provides INI file read and write functionality in Go.
16package ini
17
18import (
19 "bytes"
20 "errors"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "os"
25 "regexp"
26 "runtime"
27 "strconv"
28 "strings"
29 "sync"
30 "time"
31)
32
33const (
34 // Name for default section. You can use this constant or the string literal.
35 // In most of cases, an empty string is all you need to access the section.
36 DEFAULT_SECTION = "DEFAULT"
37
38 // Maximum allowed depth when recursively substituing variable names.
39 _DEPTH_VALUES = 99
40 _VERSION = "1.23.1"
41)
42
43// Version returns current package version literal.
44func Version() string {
45 return _VERSION
46}
47
48var (
49 // Delimiter to determine or compose a new line.
50 // This variable will be changed to "\r\n" automatically on Windows
51 // at package init time.
52 LineBreak = "\n"
53
54 // Variable regexp pattern: %(variable)s
55 varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
56
57 // Indicate whether to align "=" sign with spaces to produce pretty output
58 // or reduce all possible spaces for compact format.
59 PrettyFormat = true
60
61 // Explicitly write DEFAULT section header
62 DefaultHeader = false
63)
64
65func init() {
66 if runtime.GOOS == "windows" {
67 LineBreak = "\r\n"
68 }
69}
70
71func inSlice(str string, s []string) bool {
72 for _, v := range s {
73 if str == v {
74 return true
75 }
76 }
77 return false
78}
79
80// dataSource is an interface that returns object which can be read and closed.
81type dataSource interface {
82 ReadCloser() (io.ReadCloser, error)
83}
84
85// sourceFile represents an object that contains content on the local file system.
86type sourceFile struct {
87 name string
88}
89
90func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
91 return os.Open(s.name)
92}
93
94type bytesReadCloser struct {
95 reader io.Reader
96}
97
98func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
99 return rc.reader.Read(p)
100}
101
102func (rc *bytesReadCloser) Close() error {
103 return nil
104}
105
106// sourceData represents an object that contains content in memory.
107type sourceData struct {
108 data []byte
109}
110
111func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
112 return ioutil.NopCloser(bytes.NewReader(s.data)), nil
113}
114
115// sourceReadCloser represents an input stream with Close method.
116type sourceReadCloser struct {
117 reader io.ReadCloser
118}
119
120func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
121 return s.reader, nil
122}
123
124// File represents a combination of a or more INI file(s) in memory.
125type File struct {
126 // Should make things safe, but sometimes doesn't matter.
127 BlockMode bool
128 // Make sure data is safe in multiple goroutines.
129 lock sync.RWMutex
130
131 // Allow combination of multiple data sources.
132 dataSources []dataSource
133 // Actual data is stored here.
134 sections map[string]*Section
135
136 // To keep data in order.
137 sectionList []string
138
139 options LoadOptions
140
141 NameMapper
142 ValueMapper
143}
144
145// newFile initializes File object with given data sources.
146func newFile(dataSources []dataSource, opts LoadOptions) *File {
147 return &File{
148 BlockMode: true,
149 dataSources: dataSources,
150 sections: make(map[string]*Section),
151 sectionList: make([]string, 0, 10),
152 options: opts,
153 }
154}
155
156func parseDataSource(source interface{}) (dataSource, error) {
157 switch s := source.(type) {
158 case string:
159 return sourceFile{s}, nil
160 case []byte:
161 return &sourceData{s}, nil
162 case io.ReadCloser:
163 return &sourceReadCloser{s}, nil
164 default:
165 return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
166 }
167}
168
169type LoadOptions struct {
170 // Loose indicates whether the parser should ignore nonexistent files or return error.
171 Loose bool
172 // Insensitive indicates whether the parser forces all section and key names to lowercase.
173 Insensitive bool
174 // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
175 IgnoreContinuation bool
176 // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
177 // This type of keys are mostly used in my.cnf.
178 AllowBooleanKeys bool
179 // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
180 // conform to key/value pairs. Specify the names of those blocks here.
181 UnparseableSections []string
182}
183
184func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
185 sources := make([]dataSource, len(others)+1)
186 sources[0], err = parseDataSource(source)
187 if err != nil {
188 return nil, err
189 }
190 for i := range others {
191 sources[i+1], err = parseDataSource(others[i])
192 if err != nil {
193 return nil, err
194 }
195 }
196 f := newFile(sources, opts)
197 if err = f.Reload(); err != nil {
198 return nil, err
199 }
200 return f, nil
201}
202
203// Load loads and parses from INI data sources.
204// Arguments can be mixed of file name with string type, or raw data in []byte.
205// It will return error if list contains nonexistent files.
206func Load(source interface{}, others ...interface{}) (*File, error) {
207 return LoadSources(LoadOptions{}, source, others...)
208}
209
210// LooseLoad has exactly same functionality as Load function
211// except it ignores nonexistent files instead of returning error.
212func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
213 return LoadSources(LoadOptions{Loose: true}, source, others...)
214}
215
216// InsensitiveLoad has exactly same functionality as Load function
217// except it forces all section and key names to be lowercased.
218func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
219 return LoadSources(LoadOptions{Insensitive: true}, source, others...)
220}
221
222// Empty returns an empty file object.
223func Empty() *File {
224 // Ignore error here, we sure our data is good.
225 f, _ := Load([]byte(""))
226 return f
227}
228
229// NewSection creates a new section.
230func (f *File) NewSection(name string) (*Section, error) {
231 if len(name) == 0 {
232 return nil, errors.New("error creating new section: empty section name")
233 } else if f.options.Insensitive && name != DEFAULT_SECTION {
234 name = strings.ToLower(name)
235 }
236
237 if f.BlockMode {
238 f.lock.Lock()
239 defer f.lock.Unlock()
240 }
241
242 if inSlice(name, f.sectionList) {
243 return f.sections[name], nil
244 }
245
246 f.sectionList = append(f.sectionList, name)
247 f.sections[name] = newSection(f, name)
248 return f.sections[name], nil
249}
250
251// NewRawSection creates a new section with an unparseable body.
252func (f *File) NewRawSection(name, body string) (*Section, error) {
253 section, err := f.NewSection(name)
254 if err != nil {
255 return nil, err
256 }
257
258 section.isRawSection = true
259 section.rawBody = body
260 return section, nil
261}
262
263// NewSections creates a list of sections.
264func (f *File) NewSections(names ...string) (err error) {
265 for _, name := range names {
266 if _, err = f.NewSection(name); err != nil {
267 return err
268 }
269 }
270 return nil
271}
272
273// GetSection returns section by given name.
274func (f *File) GetSection(name string) (*Section, error) {
275 if len(name) == 0 {
276 name = DEFAULT_SECTION
277 } else if f.options.Insensitive {
278 name = strings.ToLower(name)
279 }
280
281 if f.BlockMode {
282 f.lock.RLock()
283 defer f.lock.RUnlock()
284 }
285
286 sec := f.sections[name]
287 if sec == nil {
288 return nil, fmt.Errorf("section '%s' does not exist", name)
289 }
290 return sec, nil
291}
292
293// Section assumes named section exists and returns a zero-value when not.
294func (f *File) Section(name string) *Section {
295 sec, err := f.GetSection(name)
296 if err != nil {
297 // Note: It's OK here because the only possible error is empty section name,
298 // but if it's empty, this piece of code won't be executed.
299 sec, _ = f.NewSection(name)
300 return sec
301 }
302 return sec
303}
304
305// Section returns list of Section.
306func (f *File) Sections() []*Section {
307 sections := make([]*Section, len(f.sectionList))
308 for i := range f.sectionList {
309 sections[i] = f.Section(f.sectionList[i])
310 }
311 return sections
312}
313
314// SectionStrings returns list of section names.
315func (f *File) SectionStrings() []string {
316 list := make([]string, len(f.sectionList))
317 copy(list, f.sectionList)
318 return list
319}
320
321// DeleteSection deletes a section.
322func (f *File) DeleteSection(name string) {
323 if f.BlockMode {
324 f.lock.Lock()
325 defer f.lock.Unlock()
326 }
327
328 if len(name) == 0 {
329 name = DEFAULT_SECTION
330 }
331
332 for i, s := range f.sectionList {
333 if s == name {
334 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
335 delete(f.sections, name)
336 return
337 }
338 }
339}
340
341func (f *File) reload(s dataSource) error {
342 r, err := s.ReadCloser()
343 if err != nil {
344 return err
345 }
346 defer r.Close()
347
348 return f.parse(r)
349}
350
351// Reload reloads and parses all data sources.
352func (f *File) Reload() (err error) {
353 for _, s := range f.dataSources {
354 if err = f.reload(s); err != nil {
355 // In loose mode, we create an empty default section for nonexistent files.
356 if os.IsNotExist(err) && f.options.Loose {
357 f.parse(bytes.NewBuffer(nil))
358 continue
359 }
360 return err
361 }
362 }
363 return nil
364}
365
366// Append appends one or more data sources and reloads automatically.
367func (f *File) Append(source interface{}, others ...interface{}) error {
368 ds, err := parseDataSource(source)
369 if err != nil {
370 return err
371 }
372 f.dataSources = append(f.dataSources, ds)
373 for _, s := range others {
374 ds, err = parseDataSource(s)
375 if err != nil {
376 return err
377 }
378 f.dataSources = append(f.dataSources, ds)
379 }
380 return f.Reload()
381}
382
383// WriteToIndent writes content into io.Writer with given indention.
384// If PrettyFormat has been set to be true,
385// it will align "=" sign with spaces under each section.
386func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
387 equalSign := "="
388 if PrettyFormat {
389 equalSign = " = "
390 }
391
392 // Use buffer to make sure target is safe until finish encoding.
393 buf := bytes.NewBuffer(nil)
394 for i, sname := range f.sectionList {
395 sec := f.Section(sname)
396 if len(sec.Comment) > 0 {
397 if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
398 sec.Comment = "; " + sec.Comment
399 }
400 if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
401 return 0, err
402 }
403 }
404
405 if i > 0 || DefaultHeader {
406 if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
407 return 0, err
408 }
409 } else {
410 // Write nothing if default section is empty
411 if len(sec.keyList) == 0 {
412 continue
413 }
414 }
415
416 if sec.isRawSection {
417 if _, err = buf.WriteString(sec.rawBody); err != nil {
418 return 0, err
419 }
420 continue
421 }
422
423 // Count and generate alignment length and buffer spaces using the
424 // longest key. Keys may be modifed if they contain certain characters so
425 // we need to take that into account in our calculation.
426 alignLength := 0
427 if PrettyFormat {
428 for _, kname := range sec.keyList {
429 keyLength := len(kname)
430 // First case will surround key by ` and second by """
431 if strings.ContainsAny(kname, "\"=:") {
432 keyLength += 2
433 } else if strings.Contains(kname, "`") {
434 keyLength += 6
435 }
436
437 if keyLength > alignLength {
438 alignLength = keyLength
439 }
440 }
441 }
442 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
443
444 for _, kname := range sec.keyList {
445 key := sec.Key(kname)
446 if len(key.Comment) > 0 {
447 if len(indent) > 0 && sname != DEFAULT_SECTION {
448 buf.WriteString(indent)
449 }
450 if key.Comment[0] != '#' && key.Comment[0] != ';' {
451 key.Comment = "; " + key.Comment
452 }
453 if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
454 return 0, err
455 }
456 }
457
458 if len(indent) > 0 && sname != DEFAULT_SECTION {
459 buf.WriteString(indent)
460 }
461
462 switch {
463 case key.isAutoIncrement:
464 kname = "-"
465 case strings.ContainsAny(kname, "\"=:"):
466 kname = "`" + kname + "`"
467 case strings.Contains(kname, "`"):
468 kname = `"""` + kname + `"""`
469 }
470 if _, err = buf.WriteString(kname); err != nil {
471 return 0, err
472 }
473
474 if key.isBooleanType {
475 continue
476 }
477
478 // Write out alignment spaces before "=" sign
479 if PrettyFormat {
480 buf.Write(alignSpaces[:alignLength-len(kname)])
481 }
482
483 val := key.value
484 // In case key value contains "\n", "`", "\"", "#" or ";"
485 if strings.ContainsAny(val, "\n`") {
486 val = `"""` + val + `"""`
487 } else if strings.ContainsAny(val, "#;") {
488 val = "`" + val + "`"
489 }
490 if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
491 return 0, err
492 }
493 }
494
495 // Put a line between sections
496 if _, err = buf.WriteString(LineBreak); err != nil {
497 return 0, err
498 }
499 }
500
501 return buf.WriteTo(w)
502}
503
504// WriteTo writes file content into io.Writer.
505func (f *File) WriteTo(w io.Writer) (int64, error) {
506 return f.WriteToIndent(w, "")
507}
508
509// SaveToIndent writes content to file system with given value indention.
510func (f *File) SaveToIndent(filename, indent string) error {
511 // Note: Because we are truncating with os.Create,
512 // so it's safer to save to a temporary file location and rename afte done.
513 tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
514 defer os.Remove(tmpPath)
515
516 fw, err := os.Create(tmpPath)
517 if err != nil {
518 return err
519 }
520
521 if _, err = f.WriteToIndent(fw, indent); err != nil {
522 fw.Close()
523 return err
524 }
525 fw.Close()
526
527 // Remove old file and rename the new one.
528 os.Remove(filename)
529 return os.Rename(tmpPath, filename)
530}
531
532// SaveTo writes content to file system.
533func (f *File) SaveTo(filename string) error {
534 return f.SaveToIndent(filename, "")
535}
diff --git a/vendor/github.com/go-ini/ini/key.go b/vendor/github.com/go-ini/ini/key.go
new file mode 100644
index 0000000..9738c55
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/key.go
@@ -0,0 +1,633 @@
1// Copyright 2014 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 "fmt"
19 "strconv"
20 "strings"
21 "time"
22)
23
24// Key represents a key under a section.
25type Key struct {
26 s *Section
27 name string
28 value string
29 isAutoIncrement bool
30 isBooleanType bool
31
32 Comment string
33}
34
35// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
36type ValueMapper func(string) string
37
38// Name returns name of key.
39func (k *Key) Name() string {
40 return k.name
41}
42
43// Value returns raw value of key for performance purpose.
44func (k *Key) Value() string {
45 return k.value
46}
47
48// String returns string representation of value.
49func (k *Key) String() string {
50 val := k.value
51 if k.s.f.ValueMapper != nil {
52 val = k.s.f.ValueMapper(val)
53 }
54 if strings.Index(val, "%") == -1 {
55 return val
56 }
57
58 for i := 0; i < _DEPTH_VALUES; i++ {
59 vr := varPattern.FindString(val)
60 if len(vr) == 0 {
61 break
62 }
63
64 // Take off leading '%(' and trailing ')s'.
65 noption := strings.TrimLeft(vr, "%(")
66 noption = strings.TrimRight(noption, ")s")
67
68 // Search in the same section.
69 nk, err := k.s.GetKey(noption)
70 if err != nil {
71 // Search again in default section.
72 nk, _ = k.s.f.Section("").GetKey(noption)
73 }
74
75 // Substitute by new value and take off leading '%(' and trailing ')s'.
76 val = strings.Replace(val, vr, nk.value, -1)
77 }
78 return val
79}
80
81// Validate accepts a validate function which can
82// return modifed result as key value.
83func (k *Key) Validate(fn func(string) string) string {
84 return fn(k.String())
85}
86
87// parseBool returns the boolean value represented by the string.
88//
89// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
90// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
91// Any other value returns an error.
92func parseBool(str string) (value bool, err error) {
93 switch str {
94 case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
95 return true, nil
96 case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
97 return false, nil
98 }
99 return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
100}
101
102// Bool returns bool type value.
103func (k *Key) Bool() (bool, error) {
104 return parseBool(k.String())
105}
106
107// Float64 returns float64 type value.
108func (k *Key) Float64() (float64, error) {
109 return strconv.ParseFloat(k.String(), 64)
110}
111
112// Int returns int type value.
113func (k *Key) Int() (int, error) {
114 return strconv.Atoi(k.String())
115}
116
117// Int64 returns int64 type value.
118func (k *Key) Int64() (int64, error) {
119 return strconv.ParseInt(k.String(), 10, 64)
120}
121
122// Uint returns uint type valued.
123func (k *Key) Uint() (uint, error) {
124 u, e := strconv.ParseUint(k.String(), 10, 64)
125 return uint(u), e
126}
127
128// Uint64 returns uint64 type value.
129func (k *Key) Uint64() (uint64, error) {
130 return strconv.ParseUint(k.String(), 10, 64)
131}
132
133// Duration returns time.Duration type value.
134func (k *Key) Duration() (time.Duration, error) {
135 return time.ParseDuration(k.String())
136}
137
138// TimeFormat parses with given format and returns time.Time type value.
139func (k *Key) TimeFormat(format string) (time.Time, error) {
140 return time.Parse(format, k.String())
141}
142
143// Time parses with RFC3339 format and returns time.Time type value.
144func (k *Key) Time() (time.Time, error) {
145 return k.TimeFormat(time.RFC3339)
146}
147
148// MustString returns default value if key value is empty.
149func (k *Key) MustString(defaultVal string) string {
150 val := k.String()
151 if len(val) == 0 {
152 k.value = defaultVal
153 return defaultVal
154 }
155 return val
156}
157
158// MustBool always returns value without error,
159// it returns false if error occurs.
160func (k *Key) MustBool(defaultVal ...bool) bool {
161 val, err := k.Bool()
162 if len(defaultVal) > 0 && err != nil {
163 k.value = strconv.FormatBool(defaultVal[0])
164 return defaultVal[0]
165 }
166 return val
167}
168
169// MustFloat64 always returns value without error,
170// it returns 0.0 if error occurs.
171func (k *Key) MustFloat64(defaultVal ...float64) float64 {
172 val, err := k.Float64()
173 if len(defaultVal) > 0 && err != nil {
174 k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
175 return defaultVal[0]
176 }
177 return val
178}
179
180// MustInt always returns value without error,
181// it returns 0 if error occurs.
182func (k *Key) MustInt(defaultVal ...int) int {
183 val, err := k.Int()
184 if len(defaultVal) > 0 && err != nil {
185 k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
186 return defaultVal[0]
187 }
188 return val
189}
190
191// MustInt64 always returns value without error,
192// it returns 0 if error occurs.
193func (k *Key) MustInt64(defaultVal ...int64) int64 {
194 val, err := k.Int64()
195 if len(defaultVal) > 0 && err != nil {
196 k.value = strconv.FormatInt(defaultVal[0], 10)
197 return defaultVal[0]
198 }
199 return val
200}
201
202// MustUint always returns value without error,
203// it returns 0 if error occurs.
204func (k *Key) MustUint(defaultVal ...uint) uint {
205 val, err := k.Uint()
206 if len(defaultVal) > 0 && err != nil {
207 k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
208 return defaultVal[0]
209 }
210 return val
211}
212
213// MustUint64 always returns value without error,
214// it returns 0 if error occurs.
215func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
216 val, err := k.Uint64()
217 if len(defaultVal) > 0 && err != nil {
218 k.value = strconv.FormatUint(defaultVal[0], 10)
219 return defaultVal[0]
220 }
221 return val
222}
223
224// MustDuration always returns value without error,
225// it returns zero value if error occurs.
226func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
227 val, err := k.Duration()
228 if len(defaultVal) > 0 && err != nil {
229 k.value = defaultVal[0].String()
230 return defaultVal[0]
231 }
232 return val
233}
234
235// MustTimeFormat always parses with given format and returns value without error,
236// it returns zero value if error occurs.
237func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
238 val, err := k.TimeFormat(format)
239 if len(defaultVal) > 0 && err != nil {
240 k.value = defaultVal[0].Format(format)
241 return defaultVal[0]
242 }
243 return val
244}
245
246// MustTime always parses with RFC3339 format and returns value without error,
247// it returns zero value if error occurs.
248func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
249 return k.MustTimeFormat(time.RFC3339, defaultVal...)
250}
251
252// In always returns value without error,
253// it returns default value if error occurs or doesn't fit into candidates.
254func (k *Key) In(defaultVal string, candidates []string) string {
255 val := k.String()
256 for _, cand := range candidates {
257 if val == cand {
258 return val
259 }
260 }
261 return defaultVal
262}
263
264// InFloat64 always returns value without error,
265// it returns default value if error occurs or doesn't fit into candidates.
266func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
267 val := k.MustFloat64()
268 for _, cand := range candidates {
269 if val == cand {
270 return val
271 }
272 }
273 return defaultVal
274}
275
276// InInt always returns value without error,
277// it returns default value if error occurs or doesn't fit into candidates.
278func (k *Key) InInt(defaultVal int, candidates []int) int {
279 val := k.MustInt()
280 for _, cand := range candidates {
281 if val == cand {
282 return val
283 }
284 }
285 return defaultVal
286}
287
288// InInt64 always returns value without error,
289// it returns default value if error occurs or doesn't fit into candidates.
290func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
291 val := k.MustInt64()
292 for _, cand := range candidates {
293 if val == cand {
294 return val
295 }
296 }
297 return defaultVal
298}
299
300// InUint always returns value without error,
301// it returns default value if error occurs or doesn't fit into candidates.
302func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
303 val := k.MustUint()
304 for _, cand := range candidates {
305 if val == cand {
306 return val
307 }
308 }
309 return defaultVal
310}
311
312// InUint64 always returns value without error,
313// it returns default value if error occurs or doesn't fit into candidates.
314func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
315 val := k.MustUint64()
316 for _, cand := range candidates {
317 if val == cand {
318 return val
319 }
320 }
321 return defaultVal
322}
323
324// InTimeFormat always parses with given format and returns value without error,
325// it returns default value if error occurs or doesn't fit into candidates.
326func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
327 val := k.MustTimeFormat(format)
328 for _, cand := range candidates {
329 if val == cand {
330 return val
331 }
332 }
333 return defaultVal
334}
335
336// InTime always parses with RFC3339 format and returns value without error,
337// it returns default value if error occurs or doesn't fit into candidates.
338func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
339 return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
340}
341
342// RangeFloat64 checks if value is in given range inclusively,
343// and returns default value if it's not.
344func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
345 val := k.MustFloat64()
346 if val < min || val > max {
347 return defaultVal
348 }
349 return val
350}
351
352// RangeInt checks if value is in given range inclusively,
353// and returns default value if it's not.
354func (k *Key) RangeInt(defaultVal, min, max int) int {
355 val := k.MustInt()
356 if val < min || val > max {
357 return defaultVal
358 }
359 return val
360}
361
362// RangeInt64 checks if value is in given range inclusively,
363// and returns default value if it's not.
364func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
365 val := k.MustInt64()
366 if val < min || val > max {
367 return defaultVal
368 }
369 return val
370}
371
372// RangeTimeFormat checks if value with given format is in given range inclusively,
373// and returns default value if it's not.
374func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
375 val := k.MustTimeFormat(format)
376 if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
377 return defaultVal
378 }
379 return val
380}
381
382// RangeTime checks if value with RFC3339 format is in given range inclusively,
383// and returns default value if it's not.
384func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
385 return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
386}
387
388// Strings returns list of string divided by given delimiter.
389func (k *Key) Strings(delim string) []string {
390 str := k.String()
391 if len(str) == 0 {
392 return []string{}
393 }
394
395 vals := strings.Split(str, delim)
396 for i := range vals {
397 vals[i] = strings.TrimSpace(vals[i])
398 }
399 return vals
400}
401
402// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
403func (k *Key) Float64s(delim string) []float64 {
404 vals, _ := k.getFloat64s(delim, true, false)
405 return vals
406}
407
408// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
409func (k *Key) Ints(delim string) []int {
410 vals, _ := k.getInts(delim, true, false)
411 return vals
412}
413
414// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
415func (k *Key) Int64s(delim string) []int64 {
416 vals, _ := k.getInt64s(delim, true, false)
417 return vals
418}
419
420// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
421func (k *Key) Uints(delim string) []uint {
422 vals, _ := k.getUints(delim, true, false)
423 return vals
424}
425
426// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
427func (k *Key) Uint64s(delim string) []uint64 {
428 vals, _ := k.getUint64s(delim, true, false)
429 return vals
430}
431
432// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
433// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
434func (k *Key) TimesFormat(format, delim string) []time.Time {
435 vals, _ := k.getTimesFormat(format, delim, true, false)
436 return vals
437}
438
439// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
440// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
441func (k *Key) Times(delim string) []time.Time {
442 return k.TimesFormat(time.RFC3339, delim)
443}
444
445// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
446// it will not be included to result list.
447func (k *Key) ValidFloat64s(delim string) []float64 {
448 vals, _ := k.getFloat64s(delim, false, false)
449 return vals
450}
451
452// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
453// not be included to result list.
454func (k *Key) ValidInts(delim string) []int {
455 vals, _ := k.getInts(delim, false, false)
456 return vals
457}
458
459// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
460// then it will not be included to result list.
461func (k *Key) ValidInt64s(delim string) []int64 {
462 vals, _ := k.getInt64s(delim, false, false)
463 return vals
464}
465
466// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
467// then it will not be included to result list.
468func (k *Key) ValidUints(delim string) []uint {
469 vals, _ := k.getUints(delim, false, false)
470 return vals
471}
472
473// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
474// integer, then it will not be included to result list.
475func (k *Key) ValidUint64s(delim string) []uint64 {
476 vals, _ := k.getUint64s(delim, false, false)
477 return vals
478}
479
480// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
481func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
482 vals, _ := k.getTimesFormat(format, delim, false, false)
483 return vals
484}
485
486// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
487func (k *Key) ValidTimes(delim string) []time.Time {
488 return k.ValidTimesFormat(time.RFC3339, delim)
489}
490
491// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
492func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
493 return k.getFloat64s(delim, false, true)
494}
495
496// StrictInts returns list of int divided by given delimiter or error on first invalid input.
497func (k *Key) StrictInts(delim string) ([]int, error) {
498 return k.getInts(delim, false, true)
499}
500
501// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
502func (k *Key) StrictInt64s(delim string) ([]int64, error) {
503 return k.getInt64s(delim, false, true)
504}
505
506// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
507func (k *Key) StrictUints(delim string) ([]uint, error) {
508 return k.getUints(delim, false, true)
509}
510
511// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
512func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
513 return k.getUint64s(delim, false, true)
514}
515
516// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
517// or error on first invalid input.
518func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
519 return k.getTimesFormat(format, delim, false, true)
520}
521
522// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
523// or error on first invalid input.
524func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
525 return k.StrictTimesFormat(time.RFC3339, delim)
526}
527
528// getFloat64s returns list of float64 divided by given delimiter.
529func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]float64, error) {
530 strs := k.Strings(delim)
531 vals := make([]float64, 0, len(strs))
532 for _, str := range strs {
533 val, err := strconv.ParseFloat(str, 64)
534 if err != nil && returnOnInvalid {
535 return nil, err
536 }
537 if err == nil || addInvalid {
538 vals = append(vals, val)
539 }
540 }
541 return vals, nil
542}
543
544// getInts returns list of int divided by given delimiter.
545func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, error) {
546 strs := k.Strings(delim)
547 vals := make([]int, 0, len(strs))
548 for _, str := range strs {
549 val, err := strconv.Atoi(str)
550 if err != nil && returnOnInvalid {
551 return nil, err
552 }
553 if err == nil || addInvalid {
554 vals = append(vals, val)
555 }
556 }
557 return vals, nil
558}
559
560// getInt64s returns list of int64 divided by given delimiter.
561func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64, error) {
562 strs := k.Strings(delim)
563 vals := make([]int64, 0, len(strs))
564 for _, str := range strs {
565 val, err := strconv.ParseInt(str, 10, 64)
566 if err != nil && returnOnInvalid {
567 return nil, err
568 }
569 if err == nil || addInvalid {
570 vals = append(vals, val)
571 }
572 }
573 return vals, nil
574}
575
576// getUints returns list of uint divided by given delimiter.
577func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint, error) {
578 strs := k.Strings(delim)
579 vals := make([]uint, 0, len(strs))
580 for _, str := range strs {
581 val, err := strconv.ParseUint(str, 10, 0)
582 if err != nil && returnOnInvalid {
583 return nil, err
584 }
585 if err == nil || addInvalid {
586 vals = append(vals, uint(val))
587 }
588 }
589 return vals, nil
590}
591
592// getUint64s returns list of uint64 divided by given delimiter.
593func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
594 strs := k.Strings(delim)
595 vals := make([]uint64, 0, len(strs))
596 for _, str := range strs {
597 val, err := strconv.ParseUint(str, 10, 64)
598 if err != nil && returnOnInvalid {
599 return nil, err
600 }
601 if err == nil || addInvalid {
602 vals = append(vals, val)
603 }
604 }
605 return vals, nil
606}
607
608// getTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
609func (k *Key) getTimesFormat(format, delim string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
610 strs := k.Strings(delim)
611 vals := make([]time.Time, 0, len(strs))
612 for _, str := range strs {
613 val, err := time.Parse(format, str)
614 if err != nil && returnOnInvalid {
615 return nil, err
616 }
617 if err == nil || addInvalid {
618 vals = append(vals, val)
619 }
620 }
621 return vals, nil
622}
623
624// SetValue changes key value.
625func (k *Key) SetValue(v string) {
626 if k.s.f.BlockMode {
627 k.s.f.lock.Lock()
628 defer k.s.f.lock.Unlock()
629 }
630
631 k.value = v
632 k.s.keysHash[k.name] = v
633}
diff --git a/vendor/github.com/go-ini/ini/parser.go b/vendor/github.com/go-ini/ini/parser.go
new file mode 100644
index 0000000..b0aabe3
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/parser.go
@@ -0,0 +1,356 @@
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 "strconv"
23 "strings"
24 "unicode"
25)
26
27type tokenType int
28
29const (
30 _TOKEN_INVALID tokenType = iota
31 _TOKEN_COMMENT
32 _TOKEN_SECTION
33 _TOKEN_KEY
34)
35
36type parser struct {
37 buf *bufio.Reader
38 isEOF bool
39 count int
40 comment *bytes.Buffer
41}
42
43func 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
53func (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
80func (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
92func 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
100func 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
142func (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
168func (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.
191func 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
196func (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.
243func (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 key, err := section.NewKey(string(line), "true")
322 if err != nil {
323 return err
324 }
325 key.isBooleanType = true
326 key.Comment = strings.TrimSpace(p.comment.String())
327 p.comment.Reset()
328 continue
329 }
330 return err
331 }
332
333 // Auto increment.
334 isAutoIncr := false
335 if kname == "-" {
336 isAutoIncr = true
337 kname = "#" + strconv.Itoa(p.count)
338 p.count++
339 }
340
341 key, err := section.NewKey(kname, "")
342 if err != nil {
343 return err
344 }
345 key.isAutoIncrement = isAutoIncr
346
347 value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
348 if err != nil {
349 return err
350 }
351 key.SetValue(value)
352 key.Comment = strings.TrimSpace(p.comment.String())
353 p.comment.Reset()
354 }
355 return nil
356}
diff --git a/vendor/github.com/go-ini/ini/section.go b/vendor/github.com/go-ini/ini/section.go
new file mode 100644
index 0000000..45d2f3b
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/section.go
@@ -0,0 +1,221 @@
1// Copyright 2014 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 "errors"
19 "fmt"
20 "strings"
21)
22
23// Section represents a config section.
24type Section struct {
25 f *File
26 Comment string
27 name string
28 keys map[string]*Key
29 keyList []string
30 keysHash map[string]string
31
32 isRawSection bool
33 rawBody string
34}
35
36func newSection(f *File, name string) *Section {
37 return &Section{
38 f: f,
39 name: name,
40 keys: make(map[string]*Key),
41 keyList: make([]string, 0, 10),
42 keysHash: make(map[string]string),
43 }
44}
45
46// Name returns name of Section.
47func (s *Section) Name() string {
48 return s.name
49}
50
51// Body returns rawBody of Section if the section was marked as unparseable.
52// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
53func (s *Section) Body() string {
54 return strings.TrimSpace(s.rawBody)
55}
56
57// NewKey creates a new key to given section.
58func (s *Section) NewKey(name, val string) (*Key, error) {
59 if len(name) == 0 {
60 return nil, errors.New("error creating new key: empty key name")
61 } else if s.f.options.Insensitive {
62 name = strings.ToLower(name)
63 }
64
65 if s.f.BlockMode {
66 s.f.lock.Lock()
67 defer s.f.lock.Unlock()
68 }
69
70 if inSlice(name, s.keyList) {
71 s.keys[name].value = val
72 return s.keys[name], nil
73 }
74
75 s.keyList = append(s.keyList, name)
76 s.keys[name] = &Key{
77 s: s,
78 name: name,
79 value: val,
80 }
81 s.keysHash[name] = val
82 return s.keys[name], nil
83}
84
85// GetKey returns key in section by given name.
86func (s *Section) GetKey(name string) (*Key, error) {
87 // FIXME: change to section level lock?
88 if s.f.BlockMode {
89 s.f.lock.RLock()
90 }
91 if s.f.options.Insensitive {
92 name = strings.ToLower(name)
93 }
94 key := s.keys[name]
95 if s.f.BlockMode {
96 s.f.lock.RUnlock()
97 }
98
99 if key == nil {
100 // Check if it is a child-section.
101 sname := s.name
102 for {
103 if i := strings.LastIndex(sname, "."); i > -1 {
104 sname = sname[:i]
105 sec, err := s.f.GetSection(sname)
106 if err != nil {
107 continue
108 }
109 return sec.GetKey(name)
110 } else {
111 break
112 }
113 }
114 return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
115 }
116 return key, nil
117}
118
119// HasKey returns true if section contains a key with given name.
120func (s *Section) HasKey(name string) bool {
121 key, _ := s.GetKey(name)
122 return key != nil
123}
124
125// Haskey is a backwards-compatible name for HasKey.
126func (s *Section) Haskey(name string) bool {
127 return s.HasKey(name)
128}
129
130// HasValue returns true if section contains given raw value.
131func (s *Section) HasValue(value string) bool {
132 if s.f.BlockMode {
133 s.f.lock.RLock()
134 defer s.f.lock.RUnlock()
135 }
136
137 for _, k := range s.keys {
138 if value == k.value {
139 return true
140 }
141 }
142 return false
143}
144
145// Key assumes named Key exists in section and returns a zero-value when not.
146func (s *Section) Key(name string) *Key {
147 key, err := s.GetKey(name)
148 if err != nil {
149 // It's OK here because the only possible error is empty key name,
150 // but if it's empty, this piece of code won't be executed.
151 key, _ = s.NewKey(name, "")
152 return key
153 }
154 return key
155}
156
157// Keys returns list of keys of section.
158func (s *Section) Keys() []*Key {
159 keys := make([]*Key, len(s.keyList))
160 for i := range s.keyList {
161 keys[i] = s.Key(s.keyList[i])
162 }
163 return keys
164}
165
166// ParentKeys returns list of keys of parent section.
167func (s *Section) ParentKeys() []*Key {
168 var parentKeys []*Key
169 sname := s.name
170 for {
171 if i := strings.LastIndex(sname, "."); i > -1 {
172 sname = sname[:i]
173 sec, err := s.f.GetSection(sname)
174 if err != nil {
175 continue
176 }
177 parentKeys = append(parentKeys, sec.Keys()...)
178 } else {
179 break
180 }
181
182 }
183 return parentKeys
184}
185
186// KeyStrings returns list of key names of section.
187func (s *Section) KeyStrings() []string {
188 list := make([]string, len(s.keyList))
189 copy(list, s.keyList)
190 return list
191}
192
193// KeysHash returns keys hash consisting of names and values.
194func (s *Section) KeysHash() map[string]string {
195 if s.f.BlockMode {
196 s.f.lock.RLock()
197 defer s.f.lock.RUnlock()
198 }
199
200 hash := map[string]string{}
201 for key, value := range s.keysHash {
202 hash[key] = value
203 }
204 return hash
205}
206
207// DeleteKey deletes a key from section.
208func (s *Section) DeleteKey(name string) {
209 if s.f.BlockMode {
210 s.f.lock.Lock()
211 defer s.f.lock.Unlock()
212 }
213
214 for i, k := range s.keyList {
215 if k == name {
216 s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
217 delete(s.keys, name)
218 return
219 }
220 }
221}
diff --git a/vendor/github.com/go-ini/ini/struct.go b/vendor/github.com/go-ini/ini/struct.go
new file mode 100644
index 0000000..5ef38d8
--- /dev/null
+++ b/vendor/github.com/go-ini/ini/struct.go
@@ -0,0 +1,431 @@
1// Copyright 2014 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 "bytes"
19 "errors"
20 "fmt"
21 "reflect"
22 "strings"
23 "time"
24 "unicode"
25)
26
27// NameMapper represents a ini tag name mapper.
28type NameMapper func(string) string
29
30// Built-in name getters.
31var (
32 // AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
33 AllCapsUnderscore NameMapper = func(raw string) string {
34 newstr := make([]rune, 0, len(raw))
35 for i, chr := range raw {
36 if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
37 if i > 0 {
38 newstr = append(newstr, '_')
39 }
40 }
41 newstr = append(newstr, unicode.ToUpper(chr))
42 }
43 return string(newstr)
44 }
45 // TitleUnderscore converts to format title_underscore.
46 TitleUnderscore NameMapper = func(raw string) string {
47 newstr := make([]rune, 0, len(raw))
48 for i, chr := range raw {
49 if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
50 if i > 0 {
51 newstr = append(newstr, '_')
52 }
53 chr -= ('A' - 'a')
54 }
55 newstr = append(newstr, chr)
56 }
57 return string(newstr)
58 }
59)
60
61func (s *Section) parseFieldName(raw, actual string) string {
62 if len(actual) > 0 {
63 return actual
64 }
65 if s.f.NameMapper != nil {
66 return s.f.NameMapper(raw)
67 }
68 return raw
69}
70
71func parseDelim(actual string) string {
72 if len(actual) > 0 {
73 return actual
74 }
75 return ","
76}
77
78var reflectTime = reflect.TypeOf(time.Now()).Kind()
79
80// setSliceWithProperType sets proper values to slice based on its type.
81func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
82 strs := key.Strings(delim)
83 numVals := len(strs)
84 if numVals == 0 {
85 return nil
86 }
87
88 var vals interface{}
89
90 sliceOf := field.Type().Elem().Kind()
91 switch sliceOf {
92 case reflect.String:
93 vals = strs
94 case reflect.Int:
95 vals = key.Ints(delim)
96 case reflect.Int64:
97 vals = key.Int64s(delim)
98 case reflect.Uint:
99 vals = key.Uints(delim)
100 case reflect.Uint64:
101 vals = key.Uint64s(delim)
102 case reflect.Float64:
103 vals = key.Float64s(delim)
104 case reflectTime:
105 vals = key.Times(delim)
106 default:
107 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
108 }
109
110 slice := reflect.MakeSlice(field.Type(), numVals, numVals)
111 for i := 0; i < numVals; i++ {
112 switch sliceOf {
113 case reflect.String:
114 slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
115 case reflect.Int:
116 slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
117 case reflect.Int64:
118 slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
119 case reflect.Uint:
120 slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
121 case reflect.Uint64:
122 slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
123 case reflect.Float64:
124 slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
125 case reflectTime:
126 slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
127 }
128 }
129 field.Set(slice)
130 return nil
131}
132
133// setWithProperType sets proper value to field based on its type,
134// but it does not return error for failing parsing,
135// because we want to use default value that is already assigned to strcut.
136func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
137 switch t.Kind() {
138 case reflect.String:
139 if len(key.String()) == 0 {
140 return nil
141 }
142 field.SetString(key.String())
143 case reflect.Bool:
144 boolVal, err := key.Bool()
145 if err != nil {
146 return nil
147 }
148 field.SetBool(boolVal)
149 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
150 durationVal, err := key.Duration()
151 // Skip zero value
152 if err == nil && int(durationVal) > 0 {
153 field.Set(reflect.ValueOf(durationVal))
154 return nil
155 }
156
157 intVal, err := key.Int64()
158 if err != nil || intVal == 0 {
159 return nil
160 }
161 field.SetInt(intVal)
162 // byte is an alias for uint8, so supporting uint8 breaks support for byte
163 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
164 durationVal, err := key.Duration()
165 // Skip zero value
166 if err == nil && int(durationVal) > 0 {
167 field.Set(reflect.ValueOf(durationVal))
168 return nil
169 }
170
171 uintVal, err := key.Uint64()
172 if err != nil {
173 return nil
174 }
175 field.SetUint(uintVal)
176
177 case reflect.Float32, reflect.Float64:
178 floatVal, err := key.Float64()
179 if err != nil {
180 return nil
181 }
182 field.SetFloat(floatVal)
183 case reflectTime:
184 timeVal, err := key.Time()
185 if err != nil {
186 return nil
187 }
188 field.Set(reflect.ValueOf(timeVal))
189 case reflect.Slice:
190 return setSliceWithProperType(key, field, delim)
191 default:
192 return fmt.Errorf("unsupported type '%s'", t)
193 }
194 return nil
195}
196
197func (s *Section) mapTo(val reflect.Value) error {
198 if val.Kind() == reflect.Ptr {
199 val = val.Elem()
200 }
201 typ := val.Type()
202
203 for i := 0; i < typ.NumField(); i++ {
204 field := val.Field(i)
205 tpField := typ.Field(i)
206
207 tag := tpField.Tag.Get("ini")
208 if tag == "-" {
209 continue
210 }
211
212 opts := strings.SplitN(tag, ",", 2) // strip off possible omitempty
213 fieldName := s.parseFieldName(tpField.Name, opts[0])
214 if len(fieldName) == 0 || !field.CanSet() {
215 continue
216 }
217
218 isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
219 isStruct := tpField.Type.Kind() == reflect.Struct
220 if isAnonymous {
221 field.Set(reflect.New(tpField.Type.Elem()))
222 }
223
224 if isAnonymous || isStruct {
225 if sec, err := s.f.GetSection(fieldName); err == nil {
226 if err = sec.mapTo(field); err != nil {
227 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
228 }
229 continue
230 }
231 }
232
233 if key, err := s.GetKey(fieldName); err == nil {
234 if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
235 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
236 }
237 }
238 }
239 return nil
240}
241
242// MapTo maps section to given struct.
243func (s *Section) MapTo(v interface{}) error {
244 typ := reflect.TypeOf(v)
245 val := reflect.ValueOf(v)
246 if typ.Kind() == reflect.Ptr {
247 typ = typ.Elem()
248 val = val.Elem()
249 } else {
250 return errors.New("cannot map to non-pointer struct")
251 }
252
253 return s.mapTo(val)
254}
255
256// MapTo maps file to given struct.
257func (f *File) MapTo(v interface{}) error {
258 return f.Section("").MapTo(v)
259}
260
261// MapTo maps data sources to given struct with name mapper.
262func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
263 cfg, err := Load(source, others...)
264 if err != nil {
265 return err
266 }
267 cfg.NameMapper = mapper
268 return cfg.MapTo(v)
269}
270
271// MapTo maps data sources to given struct.
272func MapTo(v, source interface{}, others ...interface{}) error {
273 return MapToWithMapper(v, nil, source, others...)
274}
275
276// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
277func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
278 slice := field.Slice(0, field.Len())
279 if field.Len() == 0 {
280 return nil
281 }
282
283 var buf bytes.Buffer
284 sliceOf := field.Type().Elem().Kind()
285 for i := 0; i < field.Len(); i++ {
286 switch sliceOf {
287 case reflect.String:
288 buf.WriteString(slice.Index(i).String())
289 case reflect.Int, reflect.Int64:
290 buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
291 case reflect.Uint, reflect.Uint64:
292 buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
293 case reflect.Float64:
294 buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
295 case reflectTime:
296 buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
297 default:
298 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
299 }
300 buf.WriteString(delim)
301 }
302 key.SetValue(buf.String()[:buf.Len()-1])
303 return nil
304}
305
306// reflectWithProperType does the opposite thing as setWithProperType.
307func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
308 switch t.Kind() {
309 case reflect.String:
310 key.SetValue(field.String())
311 case reflect.Bool:
312 key.SetValue(fmt.Sprint(field.Bool()))
313 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
314 key.SetValue(fmt.Sprint(field.Int()))
315 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
316 key.SetValue(fmt.Sprint(field.Uint()))
317 case reflect.Float32, reflect.Float64:
318 key.SetValue(fmt.Sprint(field.Float()))
319 case reflectTime:
320 key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
321 case reflect.Slice:
322 return reflectSliceWithProperType(key, field, delim)
323 default:
324 return fmt.Errorf("unsupported type '%s'", t)
325 }
326 return nil
327}
328
329// CR: copied from encoding/json/encode.go with modifications of time.Time support.
330// TODO: add more test coverage.
331func isEmptyValue(v reflect.Value) bool {
332 switch v.Kind() {
333 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
334 return v.Len() == 0
335 case reflect.Bool:
336 return !v.Bool()
337 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
338 return v.Int() == 0
339 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
340 return v.Uint() == 0
341 case reflect.Float32, reflect.Float64:
342 return v.Float() == 0
343 case reflectTime:
344 return v.Interface().(time.Time).IsZero()
345 case reflect.Interface, reflect.Ptr:
346 return v.IsNil()
347 }
348 return false
349}
350
351func (s *Section) reflectFrom(val reflect.Value) error {
352 if val.Kind() == reflect.Ptr {
353 val = val.Elem()
354 }
355 typ := val.Type()
356
357 for i := 0; i < typ.NumField(); i++ {
358 field := val.Field(i)
359 tpField := typ.Field(i)
360
361 tag := tpField.Tag.Get("ini")
362 if tag == "-" {
363 continue
364 }
365
366 opts := strings.SplitN(tag, ",", 2)
367 if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
368 continue
369 }
370
371 fieldName := s.parseFieldName(tpField.Name, opts[0])
372 if len(fieldName) == 0 || !field.CanSet() {
373 continue
374 }
375
376 if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
377 (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
378 // Note: The only error here is section doesn't exist.
379 sec, err := s.f.GetSection(fieldName)
380 if err != nil {
381 // Note: fieldName can never be empty here, ignore error.
382 sec, _ = s.f.NewSection(fieldName)
383 }
384 if err = sec.reflectFrom(field); err != nil {
385 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
386 }
387 continue
388 }
389
390 // Note: Same reason as secion.
391 key, err := s.GetKey(fieldName)
392 if err != nil {
393 key, _ = s.NewKey(fieldName, "")
394 }
395 if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
396 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
397 }
398
399 }
400 return nil
401}
402
403// ReflectFrom reflects secion from given struct.
404func (s *Section) ReflectFrom(v interface{}) error {
405 typ := reflect.TypeOf(v)
406 val := reflect.ValueOf(v)
407 if typ.Kind() == reflect.Ptr {
408 typ = typ.Elem()
409 val = val.Elem()
410 } else {
411 return errors.New("cannot reflect from non-pointer struct")
412 }
413
414 return s.reflectFrom(val)
415}
416
417// ReflectFrom reflects file from given struct.
418func (f *File) ReflectFrom(v interface{}) error {
419 return f.Section("").ReflectFrom(v)
420}
421
422// ReflectFrom reflects data sources from given struct with name mapper.
423func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
424 cfg.NameMapper = mapper
425 return cfg.ReflectFrom(v)
426}
427
428// ReflectFrom reflects data sources from given struct.
429func ReflectFrom(cfg *File, v interface{}) error {
430 return ReflectFromWithMapper(cfg, v, nil)
431}