aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/go-ini/ini
diff options
context:
space:
mode:
authorNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
committerNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
commit107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch)
treeca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/go-ini/ini
parent844b5a68d8af4791755b8f0ad293cc99f5959183 (diff)
downloadterraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/go-ini/ini')
-rw-r--r--vendor/github.com/go-ini/ini/.gitignore5
-rw-r--r--vendor/github.com/go-ini/ini/.travis.yml17
-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.md740
-rw-r--r--vendor/github.com/go-ini/ini/README_ZH.md727
-rw-r--r--vendor/github.com/go-ini/ini/error.go32
-rw-r--r--vendor/github.com/go-ini/ini/ini.go549
-rw-r--r--vendor/github.com/go-ini/ini/key.go703
-rw-r--r--vendor/github.com/go-ini/ini/parser.go358
-rw-r--r--vendor/github.com/go-ini/ini/section.go234
-rw-r--r--vendor/github.com/go-ini/ini/struct.go450
12 files changed, 0 insertions, 4018 deletions
diff --git a/vendor/github.com/go-ini/ini/.gitignore b/vendor/github.com/go-ini/ini/.gitignore
deleted file mode 100644
index c5203bf..0000000
--- a/vendor/github.com/go-ini/ini/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
1testdata/conf_out.ini
2ini.sublime-project
3ini.sublime-workspace
4testdata/conf_reflect.ini
5.idea
diff --git a/vendor/github.com/go-ini/ini/.travis.yml b/vendor/github.com/go-ini/ini/.travis.yml
deleted file mode 100644
index 65c872b..0000000
--- a/vendor/github.com/go-ini/ini/.travis.yml
+++ /dev/null
@@ -1,17 +0,0 @@
1sudo: false
2language: go
3go:
4 - 1.4.x
5 - 1.5.x
6 - 1.6.x
7 - 1.7.x
8 - master
9
10script:
11 - go get golang.org/x/tools/cmd/cover
12 - go get github.com/smartystreets/goconvey
13 - go test -v -cover -race
14
15notifications:
16 email:
17 - u@gogs.io
diff --git a/vendor/github.com/go-ini/ini/LICENSE b/vendor/github.com/go-ini/ini/LICENSE
deleted file mode 100644
index 37ec93a..0000000
--- a/vendor/github.com/go-ini/ini/LICENSE
+++ /dev/null
@@ -1,191 +0,0 @@
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
deleted file mode 100644
index ac034e5..0000000
--- a/vendor/github.com/go-ini/ini/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
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
deleted file mode 100644
index 8594742..0000000
--- a/vendor/github.com/go-ini/ini/README.md
+++ /dev/null
@@ -1,740 +0,0 @@
1INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
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
109To generate such keys in your program, you could use `NewBooleanKey`:
110
111```go
112key, err := sec.NewBooleanKey("skip-host-cache")
113```
114
115#### Comment
116
117Take care that following format will be treated as comment:
118
1191. Line begins with `#` or `;`
1202. Words after `#` or `;`
1213. Words after section name (i.e words after `[some section name]`)
122
123If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
124
125### Working with sections
126
127To get a section, you would need to:
128
129```go
130section, err := cfg.GetSection("section name")
131```
132
133For a shortcut for default section, just give an empty string as name:
134
135```go
136section, err := cfg.GetSection("")
137```
138
139When you're pretty sure the section exists, following code could make your life easier:
140
141```go
142section := cfg.Section("section name")
143```
144
145What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
146
147To create a new section:
148
149```go
150err := cfg.NewSection("new section")
151```
152
153To get a list of sections or section names:
154
155```go
156sections := cfg.Sections()
157names := cfg.SectionStrings()
158```
159
160### Working with keys
161
162To get a key under a section:
163
164```go
165key, err := cfg.Section("").GetKey("key name")
166```
167
168Same rule applies to key operations:
169
170```go
171key := cfg.Section("").Key("key name")
172```
173
174To check if a key exists:
175
176```go
177yes := cfg.Section("").HasKey("key name")
178```
179
180To create a new key:
181
182```go
183err := cfg.Section("").NewKey("name", "value")
184```
185
186To get a list of keys or key names:
187
188```go
189keys := cfg.Section("").Keys()
190names := cfg.Section("").KeyStrings()
191```
192
193To get a clone hash of keys and corresponding values:
194
195```go
196hash := cfg.Section("").KeysHash()
197```
198
199### Working with values
200
201To get a string value:
202
203```go
204val := cfg.Section("").Key("key name").String()
205```
206
207To validate key value on the fly:
208
209```go
210val := cfg.Section("").Key("key name").Validate(func(in string) string {
211 if len(in) == 0 {
212 return "default"
213 }
214 return in
215})
216```
217
218If 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):
219
220```go
221val := cfg.Section("").Key("key name").Value()
222```
223
224To check if raw value exists:
225
226```go
227yes := cfg.Section("").HasValue("test value")
228```
229
230To get value with types:
231
232```go
233// For boolean values:
234// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
235// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
236v, err = cfg.Section("").Key("BOOL").Bool()
237v, err = cfg.Section("").Key("FLOAT64").Float64()
238v, err = cfg.Section("").Key("INT").Int()
239v, err = cfg.Section("").Key("INT64").Int64()
240v, err = cfg.Section("").Key("UINT").Uint()
241v, err = cfg.Section("").Key("UINT64").Uint64()
242v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
243v, err = cfg.Section("").Key("TIME").Time() // RFC3339
244
245v = cfg.Section("").Key("BOOL").MustBool()
246v = cfg.Section("").Key("FLOAT64").MustFloat64()
247v = cfg.Section("").Key("INT").MustInt()
248v = cfg.Section("").Key("INT64").MustInt64()
249v = cfg.Section("").Key("UINT").MustUint()
250v = cfg.Section("").Key("UINT64").MustUint64()
251v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
252v = cfg.Section("").Key("TIME").MustTime() // RFC3339
253
254// Methods start with Must also accept one argument for default value
255// when key not found or fail to parse value to given type.
256// Except method MustString, which you have to pass a default value.
257
258v = cfg.Section("").Key("String").MustString("default")
259v = cfg.Section("").Key("BOOL").MustBool(true)
260v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
261v = cfg.Section("").Key("INT").MustInt(10)
262v = cfg.Section("").Key("INT64").MustInt64(99)
263v = cfg.Section("").Key("UINT").MustUint(3)
264v = cfg.Section("").Key("UINT64").MustUint64(6)
265v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
266v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
267```
268
269What if my value is three-line long?
270
271```ini
272[advance]
273ADDRESS = """404 road,
274NotFound, State, 5000
275Earth"""
276```
277
278Not a problem!
279
280```go
281cfg.Section("advance").Key("ADDRESS").String()
282
283/* --- start ---
284404 road,
285NotFound, State, 5000
286Earth
287------ end --- */
288```
289
290That's cool, how about continuation lines?
291
292```ini
293[advance]
294two_lines = how about \
295 continuation lines?
296lots_of_lines = 1 \
297 2 \
298 3 \
299 4
300```
301
302Piece of cake!
303
304```go
305cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
306cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
307```
308
309Well, I hate continuation lines, how do I disable that?
310
311```go
312cfg, err := ini.LoadSources(ini.LoadOptions{
313 IgnoreContinuation: true,
314}, "filename")
315```
316
317Holy crap!
318
319Note that single quotes around values will be stripped:
320
321```ini
322foo = "some value" // foo: some value
323bar = 'some value' // bar: some value
324```
325
326That's all? Hmm, no.
327
328#### Helper methods of working with values
329
330To get value with given candidates:
331
332```go
333v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
334v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
335v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
336v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
337v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
338v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
339v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
340v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
341```
342
343Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
344
345To validate value in a given range:
346
347```go
348vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
349vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
350vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
351vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
352vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
353vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
354vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
355```
356
357##### Auto-split values into a slice
358
359To use zero value of type for invalid inputs:
360
361```go
362// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
363// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
364vals = cfg.Section("").Key("STRINGS").Strings(",")
365vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
366vals = cfg.Section("").Key("INTS").Ints(",")
367vals = cfg.Section("").Key("INT64S").Int64s(",")
368vals = cfg.Section("").Key("UINTS").Uints(",")
369vals = cfg.Section("").Key("UINT64S").Uint64s(",")
370vals = cfg.Section("").Key("TIMES").Times(",")
371```
372
373To exclude invalid values out of result slice:
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 -> [2.2]
378vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
379vals = cfg.Section("").Key("INTS").ValidInts(",")
380vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
381vals = cfg.Section("").Key("UINTS").ValidUints(",")
382vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
383vals = cfg.Section("").Key("TIMES").ValidTimes(",")
384```
385
386Or to return nothing but error when have invalid inputs:
387
388```go
389// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
390// Input: how, 2.2, are, you -> error
391vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
392vals = cfg.Section("").Key("INTS").StrictInts(",")
393vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
394vals = cfg.Section("").Key("UINTS").StrictUints(",")
395vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
396vals = cfg.Section("").Key("TIMES").StrictTimes(",")
397```
398
399### Save your configuration
400
401Finally, it's time to save your configuration to somewhere.
402
403A typical way to save configuration is writing it to a file:
404
405```go
406// ...
407err = cfg.SaveTo("my.ini")
408err = cfg.SaveToIndent("my.ini", "\t")
409```
410
411Another way to save is writing to a `io.Writer` interface:
412
413```go
414// ...
415cfg.WriteTo(writer)
416cfg.WriteToIndent(writer, "\t")
417```
418
419By default, spaces are used to align "=" sign between key and values, to disable that:
420
421```go
422ini.PrettyFormat = false
423```
424
425## Advanced Usage
426
427### Recursive Values
428
429For 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.
430
431```ini
432NAME = ini
433
434[author]
435NAME = Unknwon
436GITHUB = https://github.com/%(NAME)s
437
438[package]
439FULL_NAME = github.com/go-ini/%(NAME)s
440```
441
442```go
443cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
444cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
445```
446
447### Parent-child Sections
448
449You 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.
450
451```ini
452NAME = ini
453VERSION = v1
454IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
455
456[package]
457CLONE_URL = https://%(IMPORT_PATH)s
458
459[package.sub]
460```
461
462```go
463cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
464```
465
466#### Retrieve parent keys available to a child section
467
468```go
469cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
470```
471
472### Unparseable Sections
473
474Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
475
476```go
477cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
478<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
479
480body := cfg.Section("COMMENTS").Body()
481
482/* --- start ---
483<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
484------ end --- */
485```
486
487### Auto-increment Key Names
488
489If 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.
490
491```ini
492[features]
493-: Support read/write comments of keys and sections
494-: Support auto-increment of key names
495-: Support load multiple files to overwrite key values
496```
497
498```go
499cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
500```
501
502### Map To Struct
503
504Want more objective way to play with INI? Cool.
505
506```ini
507Name = Unknwon
508age = 21
509Male = true
510Born = 1993-01-01T20:17:05Z
511
512[Note]
513Content = Hi is a good man!
514Cities = HangZhou, Boston
515```
516
517```go
518type Note struct {
519 Content string
520 Cities []string
521}
522
523type Person struct {
524 Name string
525 Age int `ini:"age"`
526 Male bool
527 Born time.Time
528 Note
529 Created time.Time `ini:"-"`
530}
531
532func main() {
533 cfg, err := ini.Load("path/to/ini")
534 // ...
535 p := new(Person)
536 err = cfg.MapTo(p)
537 // ...
538
539 // Things can be simpler.
540 err = ini.MapTo(p, "path/to/ini")
541 // ...
542
543 // Just map a section? Fine.
544 n := new(Note)
545 err = cfg.Section("Note").MapTo(n)
546 // ...
547}
548```
549
550Can I have default value for field? Absolutely.
551
552Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
553
554```go
555// ...
556p := &Person{
557 Name: "Joe",
558}
559// ...
560```
561
562It's really cool, but what's the point if you can't give me my file back from struct?
563
564### Reflect From Struct
565
566Why not?
567
568```go
569type Embeded struct {
570 Dates []time.Time `delim:"|"`
571 Places []string `ini:"places,omitempty"`
572 None []int `ini:",omitempty"`
573}
574
575type Author struct {
576 Name string `ini:"NAME"`
577 Male bool
578 Age int
579 GPA float64
580 NeverMind string `ini:"-"`
581 *Embeded
582}
583
584func main() {
585 a := &Author{"Unknwon", true, 21, 2.8, "",
586 &Embeded{
587 []time.Time{time.Now(), time.Now()},
588 []string{"HangZhou", "Boston"},
589 []int{},
590 }}
591 cfg := ini.Empty()
592 err = ini.ReflectFrom(cfg, a)
593 // ...
594}
595```
596
597So, what do I get?
598
599```ini
600NAME = Unknwon
601Male = true
602Age = 21
603GPA = 2.8
604
605[Embeded]
606Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
607places = HangZhou,Boston
608```
609
610#### Name Mapper
611
612To 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.
613
614There are 2 built-in name mappers:
615
616- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
617- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
618
619To use them:
620
621```go
622type Info struct {
623 PackageName string
624}
625
626func main() {
627 err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
628 // ...
629
630 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
631 // ...
632 info := new(Info)
633 cfg.NameMapper = ini.AllCapsUnderscore
634 err = cfg.MapTo(info)
635 // ...
636}
637```
638
639Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
640
641#### Value Mapper
642
643To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
644
645```go
646type Env struct {
647 Foo string `ini:"foo"`
648}
649
650func main() {
651 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
652 cfg.ValueMapper = os.ExpandEnv
653 // ...
654 env := &Env{}
655 err = cfg.Section("env").MapTo(env)
656}
657```
658
659This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
660
661#### Other Notes On Map/Reflect
662
663Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
664
665```go
666type Child struct {
667 Age string
668}
669
670type Parent struct {
671 Name string
672 Child
673}
674
675type Config struct {
676 City string
677 Parent
678}
679```
680
681Example configuration:
682
683```ini
684City = Boston
685
686[Parent]
687Name = Unknwon
688
689[Child]
690Age = 21
691```
692
693What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
694
695```go
696type Child struct {
697 Age string
698}
699
700type Parent struct {
701 Name string
702 Child `ini:"Parent"`
703}
704
705type Config struct {
706 City string
707 Parent
708}
709```
710
711Example configuration:
712
713```ini
714City = Boston
715
716[Parent]
717Name = Unknwon
718Age = 21
719```
720
721## Getting Help
722
723- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
724- [File An Issue](https://github.com/go-ini/ini/issues/new)
725
726## FAQs
727
728### What does `BlockMode` field do?
729
730By 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.
731
732### Why another INI library?
733
734Many 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.
735
736To 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)
737
738## License
739
740This 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
deleted file mode 100644
index 163432d..0000000
--- a/vendor/github.com/go-ini/ini/README_ZH.md
+++ /dev/null
@@ -1,727 +0,0 @@
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如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
103
104```go
105key, err := sec.NewBooleanKey("skip-host-cache")
106```
107
108#### 关于注释
109
110下述几种情况的内容将被视为注释:
111
1121. 所有以 `#` 或 `;` 开头的行
1132. 所有在 `#` 或 `;` 之后的内容
1143. 分区标签后的文字 (即 `[分区名]` 之后的内容)
115
116如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
117
118### 操作分区(Section)
119
120获取指定分区:
121
122```go
123section, err := cfg.GetSection("section name")
124```
125
126如果您想要获取默认分区,则可以用空字符串代替分区名:
127
128```go
129section, err := cfg.GetSection("")
130```
131
132当您非常确定某个分区是存在的,可以使用以下简便方法:
133
134```go
135section := cfg.Section("section name")
136```
137
138如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
139
140创建一个分区:
141
142```go
143err := cfg.NewSection("new section")
144```
145
146获取所有分区对象或名称:
147
148```go
149sections := cfg.Sections()
150names := cfg.SectionStrings()
151```
152
153### 操作键(Key)
154
155获取某个分区下的键:
156
157```go
158key, err := cfg.Section("").GetKey("key name")
159```
160
161和分区一样,您也可以直接获取键而忽略错误处理:
162
163```go
164key := cfg.Section("").Key("key name")
165```
166
167判断某个键是否存在:
168
169```go
170yes := cfg.Section("").HasKey("key name")
171```
172
173创建一个新的键:
174
175```go
176err := cfg.Section("").NewKey("name", "value")
177```
178
179获取分区下的所有键或键名:
180
181```go
182keys := cfg.Section("").Keys()
183names := cfg.Section("").KeyStrings()
184```
185
186获取分区下的所有键值对的克隆:
187
188```go
189hash := cfg.Section("").KeysHash()
190```
191
192### 操作键值(Value)
193
194获取一个类型为字符串(string)的值:
195
196```go
197val := cfg.Section("").Key("key name").String()
198```
199
200获取值的同时通过自定义函数进行处理验证:
201
202```go
203val := cfg.Section("").Key("key name").Validate(func(in string) string {
204 if len(in) == 0 {
205 return "default"
206 }
207 return in
208})
209```
210
211如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
212
213```go
214val := cfg.Section("").Key("key name").Value()
215```
216
217判断某个原值是否存在:
218
219```go
220yes := cfg.Section("").HasValue("test value")
221```
222
223获取其它类型的值:
224
225```go
226// 布尔值的规则:
227// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
228// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
229v, err = cfg.Section("").Key("BOOL").Bool()
230v, err = cfg.Section("").Key("FLOAT64").Float64()
231v, err = cfg.Section("").Key("INT").Int()
232v, err = cfg.Section("").Key("INT64").Int64()
233v, err = cfg.Section("").Key("UINT").Uint()
234v, err = cfg.Section("").Key("UINT64").Uint64()
235v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
236v, err = cfg.Section("").Key("TIME").Time() // RFC3339
237
238v = cfg.Section("").Key("BOOL").MustBool()
239v = cfg.Section("").Key("FLOAT64").MustFloat64()
240v = cfg.Section("").Key("INT").MustInt()
241v = cfg.Section("").Key("INT64").MustInt64()
242v = cfg.Section("").Key("UINT").MustUint()
243v = cfg.Section("").Key("UINT64").MustUint64()
244v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
245v = cfg.Section("").Key("TIME").MustTime() // RFC3339
246
247// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
248// 当键不存在或者转换失败时,则会直接返回该默认值。
249// 但是,MustString 方法必须传递一个默认值。
250
251v = cfg.Seciont("").Key("String").MustString("default")
252v = cfg.Section("").Key("BOOL").MustBool(true)
253v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
254v = cfg.Section("").Key("INT").MustInt(10)
255v = cfg.Section("").Key("INT64").MustInt64(99)
256v = cfg.Section("").Key("UINT").MustUint(3)
257v = cfg.Section("").Key("UINT64").MustUint64(6)
258v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
259v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
260```
261
262如果我的值有好多行怎么办?
263
264```ini
265[advance]
266ADDRESS = """404 road,
267NotFound, State, 5000
268Earth"""
269```
270
271嗯哼?小 case!
272
273```go
274cfg.Section("advance").Key("ADDRESS").String()
275
276/* --- start ---
277404 road,
278NotFound, State, 5000
279Earth
280------ end --- */
281```
282
283赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
284
285```ini
286[advance]
287two_lines = how about \
288 continuation lines?
289lots_of_lines = 1 \
290 2 \
291 3 \
292 4
293```
294
295简直是小菜一碟!
296
297```go
298cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
299cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
300```
301
302可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
303
304```go
305cfg, err := ini.LoadSources(ini.LoadOptions{
306 IgnoreContinuation: true,
307}, "filename")
308```
309
310哇靠给力啊!
311
312需要注意的是,值两侧的单引号会被自动剔除:
313
314```ini
315foo = "some value" // foo: some value
316bar = 'some value' // bar: some value
317```
318
319这就是全部了?哈哈,当然不是。
320
321#### 操作键值的辅助方法
322
323获取键值时设定候选值:
324
325```go
326v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
327v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
328v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
329v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
330v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
331v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
332v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
333v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
334```
335
336如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
337
338验证获取的值是否在指定范围内:
339
340```go
341vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
342vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
343vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
344vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
345vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
346vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
347vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
348```
349
350##### 自动分割键值到切片(slice)
351
352当存在无效输入时,使用零值代替:
353
354```go
355// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
356// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
357vals = cfg.Section("").Key("STRINGS").Strings(",")
358vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
359vals = cfg.Section("").Key("INTS").Ints(",")
360vals = cfg.Section("").Key("INT64S").Int64s(",")
361vals = cfg.Section("").Key("UINTS").Uints(",")
362vals = cfg.Section("").Key("UINT64S").Uint64s(",")
363vals = cfg.Section("").Key("TIMES").Times(",")
364```
365
366从结果切片中剔除无效输入:
367
368```go
369// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
370// Input: how, 2.2, are, you -> [2.2]
371vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
372vals = cfg.Section("").Key("INTS").ValidInts(",")
373vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
374vals = cfg.Section("").Key("UINTS").ValidUints(",")
375vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
376vals = cfg.Section("").Key("TIMES").ValidTimes(",")
377```
378
379当存在无效输入时,直接返回错误:
380
381```go
382// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
383// Input: how, 2.2, are, you -> error
384vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
385vals = cfg.Section("").Key("INTS").StrictInts(",")
386vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
387vals = cfg.Section("").Key("UINTS").StrictUints(",")
388vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
389vals = cfg.Section("").Key("TIMES").StrictTimes(",")
390```
391
392### 保存配置
393
394终于到了这个时刻,是时候保存一下配置了。
395
396比较原始的做法是输出配置到某个文件:
397
398```go
399// ...
400err = cfg.SaveTo("my.ini")
401err = cfg.SaveToIndent("my.ini", "\t")
402```
403
404另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
405
406```go
407// ...
408cfg.WriteTo(writer)
409cfg.WriteToIndent(writer, "\t")
410```
411
412默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
413
414```go
415ini.PrettyFormat = false
416```
417
418## 高级用法
419
420### 递归读取键值
421
422在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
423
424```ini
425NAME = ini
426
427[author]
428NAME = Unknwon
429GITHUB = https://github.com/%(NAME)s
430
431[package]
432FULL_NAME = github.com/go-ini/%(NAME)s
433```
434
435```go
436cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
437cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
438```
439
440### 读取父子分区
441
442您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
443
444```ini
445NAME = ini
446VERSION = v1
447IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
448
449[package]
450CLONE_URL = https://%(IMPORT_PATH)s
451
452[package.sub]
453```
454
455```go
456cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
457```
458
459#### 获取上级父分区下的所有键名
460
461```go
462cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
463```
464
465### 无法解析的分区
466
467如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
468
469```go
470cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
471<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
472
473body := cfg.Section("COMMENTS").Body()
474
475/* --- start ---
476<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
477------ end --- */
478```
479
480### 读取自增键名
481
482如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
483
484```ini
485[features]
486-: Support read/write comments of keys and sections
487-: Support auto-increment of key names
488-: Support load multiple files to overwrite key values
489```
490
491```go
492cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
493```
494
495### 映射到结构
496
497想要使用更加面向对象的方式玩转 INI 吗?好主意。
498
499```ini
500Name = Unknwon
501age = 21
502Male = true
503Born = 1993-01-01T20:17:05Z
504
505[Note]
506Content = Hi is a good man!
507Cities = HangZhou, Boston
508```
509
510```go
511type Note struct {
512 Content string
513 Cities []string
514}
515
516type Person struct {
517 Name string
518 Age int `ini:"age"`
519 Male bool
520 Born time.Time
521 Note
522 Created time.Time `ini:"-"`
523}
524
525func main() {
526 cfg, err := ini.Load("path/to/ini")
527 // ...
528 p := new(Person)
529 err = cfg.MapTo(p)
530 // ...
531
532 // 一切竟可以如此的简单。
533 err = ini.MapTo(p, "path/to/ini")
534 // ...
535
536 // 嗯哼?只需要映射一个分区吗?
537 n := new(Note)
538 err = cfg.Section("Note").MapTo(n)
539 // ...
540}
541```
542
543结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
544
545```go
546// ...
547p := &Person{
548 Name: "Joe",
549}
550// ...
551```
552
553这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
554
555### 从结构反射
556
557可是,我有说不能吗?
558
559```go
560type Embeded struct {
561 Dates []time.Time `delim:"|"`
562 Places []string `ini:"places,omitempty"`
563 None []int `ini:",omitempty"`
564}
565
566type Author struct {
567 Name string `ini:"NAME"`
568 Male bool
569 Age int
570 GPA float64
571 NeverMind string `ini:"-"`
572 *Embeded
573}
574
575func main() {
576 a := &Author{"Unknwon", true, 21, 2.8, "",
577 &Embeded{
578 []time.Time{time.Now(), time.Now()},
579 []string{"HangZhou", "Boston"},
580 []int{},
581 }}
582 cfg := ini.Empty()
583 err = ini.ReflectFrom(cfg, a)
584 // ...
585}
586```
587
588瞧瞧,奇迹发生了。
589
590```ini
591NAME = Unknwon
592Male = true
593Age = 21
594GPA = 2.8
595
596[Embeded]
597Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
598places = HangZhou,Boston
599```
600
601#### 名称映射器(Name Mapper)
602
603为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
604
605目前有 2 款内置的映射器:
606
607- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
608- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
609
610使用方法:
611
612```go
613type Info struct{
614 PackageName string
615}
616
617func main() {
618 err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
619 // ...
620
621 cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
622 // ...
623 info := new(Info)
624 cfg.NameMapper = ini.AllCapsUnderscore
625 err = cfg.MapTo(info)
626 // ...
627}
628```
629
630使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
631
632#### 值映射器(Value Mapper)
633
634值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
635
636```go
637type Env struct {
638 Foo string `ini:"foo"`
639}
640
641func main() {
642 cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
643 cfg.ValueMapper = os.ExpandEnv
644 // ...
645 env := &Env{}
646 err = cfg.Section("env").MapTo(env)
647}
648```
649
650本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
651
652#### 映射/反射的其它说明
653
654任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
655
656```go
657type Child struct {
658 Age string
659}
660
661type Parent struct {
662 Name string
663 Child
664}
665
666type Config struct {
667 City string
668 Parent
669}
670```
671
672示例配置文件:
673
674```ini
675City = Boston
676
677[Parent]
678Name = Unknwon
679
680[Child]
681Age = 21
682```
683
684很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
685
686```go
687type Child struct {
688 Age string
689}
690
691type Parent struct {
692 Name string
693 Child `ini:"Parent"`
694}
695
696type Config struct {
697 City string
698 Parent
699}
700```
701
702示例配置文件:
703
704```ini
705City = Boston
706
707[Parent]
708Name = Unknwon
709Age = 21
710```
711
712## 获取帮助
713
714- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
715- [创建工单](https://github.com/go-ini/ini/issues/new)
716
717## 常见问题
718
719### 字段 `BlockMode` 是什么?
720
721默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
722
723### 为什么要写另一个 INI 解析库?
724
725许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
726
727为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
diff --git a/vendor/github.com/go-ini/ini/error.go b/vendor/github.com/go-ini/ini/error.go
deleted file mode 100644
index 80afe74..0000000
--- a/vendor/github.com/go-ini/ini/error.go
+++ /dev/null
@@ -1,32 +0,0 @@
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
deleted file mode 100644
index 68d73aa..0000000
--- a/vendor/github.com/go-ini/ini/ini.go
+++ /dev/null
@@ -1,549 +0,0 @@
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.25.4"
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 // AllowShadows indicates whether to keep track of keys with same name under same section.
180 AllowShadows bool
181 // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
182 // conform to key/value pairs. Specify the names of those blocks here.
183 UnparseableSections []string
184}
185
186func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
187 sources := make([]dataSource, len(others)+1)
188 sources[0], err = parseDataSource(source)
189 if err != nil {
190 return nil, err
191 }
192 for i := range others {
193 sources[i+1], err = parseDataSource(others[i])
194 if err != nil {
195 return nil, err
196 }
197 }
198 f := newFile(sources, opts)
199 if err = f.Reload(); err != nil {
200 return nil, err
201 }
202 return f, nil
203}
204
205// Load loads and parses from INI data sources.
206// Arguments can be mixed of file name with string type, or raw data in []byte.
207// It will return error if list contains nonexistent files.
208func Load(source interface{}, others ...interface{}) (*File, error) {
209 return LoadSources(LoadOptions{}, source, others...)
210}
211
212// LooseLoad has exactly same functionality as Load function
213// except it ignores nonexistent files instead of returning error.
214func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
215 return LoadSources(LoadOptions{Loose: true}, source, others...)
216}
217
218// InsensitiveLoad has exactly same functionality as Load function
219// except it forces all section and key names to be lowercased.
220func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
221 return LoadSources(LoadOptions{Insensitive: true}, source, others...)
222}
223
224// InsensitiveLoad has exactly same functionality as Load function
225// except it allows have shadow keys.
226func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
227 return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
228}
229
230// Empty returns an empty file object.
231func Empty() *File {
232 // Ignore error here, we sure our data is good.
233 f, _ := Load([]byte(""))
234 return f
235}
236
237// NewSection creates a new section.
238func (f *File) NewSection(name string) (*Section, error) {
239 if len(name) == 0 {
240 return nil, errors.New("error creating new section: empty section name")
241 } else if f.options.Insensitive && name != DEFAULT_SECTION {
242 name = strings.ToLower(name)
243 }
244
245 if f.BlockMode {
246 f.lock.Lock()
247 defer f.lock.Unlock()
248 }
249
250 if inSlice(name, f.sectionList) {
251 return f.sections[name], nil
252 }
253
254 f.sectionList = append(f.sectionList, name)
255 f.sections[name] = newSection(f, name)
256 return f.sections[name], nil
257}
258
259// NewRawSection creates a new section with an unparseable body.
260func (f *File) NewRawSection(name, body string) (*Section, error) {
261 section, err := f.NewSection(name)
262 if err != nil {
263 return nil, err
264 }
265
266 section.isRawSection = true
267 section.rawBody = body
268 return section, nil
269}
270
271// NewSections creates a list of sections.
272func (f *File) NewSections(names ...string) (err error) {
273 for _, name := range names {
274 if _, err = f.NewSection(name); err != nil {
275 return err
276 }
277 }
278 return nil
279}
280
281// GetSection returns section by given name.
282func (f *File) GetSection(name string) (*Section, error) {
283 if len(name) == 0 {
284 name = DEFAULT_SECTION
285 } else if f.options.Insensitive {
286 name = strings.ToLower(name)
287 }
288
289 if f.BlockMode {
290 f.lock.RLock()
291 defer f.lock.RUnlock()
292 }
293
294 sec := f.sections[name]
295 if sec == nil {
296 return nil, fmt.Errorf("section '%s' does not exist", name)
297 }
298 return sec, nil
299}
300
301// Section assumes named section exists and returns a zero-value when not.
302func (f *File) Section(name string) *Section {
303 sec, err := f.GetSection(name)
304 if err != nil {
305 // Note: It's OK here because the only possible error is empty section name,
306 // but if it's empty, this piece of code won't be executed.
307 sec, _ = f.NewSection(name)
308 return sec
309 }
310 return sec
311}
312
313// Section returns list of Section.
314func (f *File) Sections() []*Section {
315 sections := make([]*Section, len(f.sectionList))
316 for i := range f.sectionList {
317 sections[i] = f.Section(f.sectionList[i])
318 }
319 return sections
320}
321
322// SectionStrings returns list of section names.
323func (f *File) SectionStrings() []string {
324 list := make([]string, len(f.sectionList))
325 copy(list, f.sectionList)
326 return list
327}
328
329// DeleteSection deletes a section.
330func (f *File) DeleteSection(name string) {
331 if f.BlockMode {
332 f.lock.Lock()
333 defer f.lock.Unlock()
334 }
335
336 if len(name) == 0 {
337 name = DEFAULT_SECTION
338 }
339
340 for i, s := range f.sectionList {
341 if s == name {
342 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
343 delete(f.sections, name)
344 return
345 }
346 }
347}
348
349func (f *File) reload(s dataSource) error {
350 r, err := s.ReadCloser()
351 if err != nil {
352 return err
353 }
354 defer r.Close()
355
356 return f.parse(r)
357}
358
359// Reload reloads and parses all data sources.
360func (f *File) Reload() (err error) {
361 for _, s := range f.dataSources {
362 if err = f.reload(s); err != nil {
363 // In loose mode, we create an empty default section for nonexistent files.
364 if os.IsNotExist(err) && f.options.Loose {
365 f.parse(bytes.NewBuffer(nil))
366 continue
367 }
368 return err
369 }
370 }
371 return nil
372}
373
374// Append appends one or more data sources and reloads automatically.
375func (f *File) Append(source interface{}, others ...interface{}) error {
376 ds, err := parseDataSource(source)
377 if err != nil {
378 return err
379 }
380 f.dataSources = append(f.dataSources, ds)
381 for _, s := range others {
382 ds, err = parseDataSource(s)
383 if err != nil {
384 return err
385 }
386 f.dataSources = append(f.dataSources, ds)
387 }
388 return f.Reload()
389}
390
391// WriteToIndent writes content into io.Writer with given indention.
392// If PrettyFormat has been set to be true,
393// it will align "=" sign with spaces under each section.
394func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
395 equalSign := "="
396 if PrettyFormat {
397 equalSign = " = "
398 }
399
400 // Use buffer to make sure target is safe until finish encoding.
401 buf := bytes.NewBuffer(nil)
402 for i, sname := range f.sectionList {
403 sec := f.Section(sname)
404 if len(sec.Comment) > 0 {
405 if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
406 sec.Comment = "; " + sec.Comment
407 }
408 if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
409 return 0, err
410 }
411 }
412
413 if i > 0 || DefaultHeader {
414 if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
415 return 0, err
416 }
417 } else {
418 // Write nothing if default section is empty
419 if len(sec.keyList) == 0 {
420 continue
421 }
422 }
423
424 if sec.isRawSection {
425 if _, err = buf.WriteString(sec.rawBody); err != nil {
426 return 0, err
427 }
428 continue
429 }
430
431 // Count and generate alignment length and buffer spaces using the
432 // longest key. Keys may be modifed if they contain certain characters so
433 // we need to take that into account in our calculation.
434 alignLength := 0
435 if PrettyFormat {
436 for _, kname := range sec.keyList {
437 keyLength := len(kname)
438 // First case will surround key by ` and second by """
439 if strings.ContainsAny(kname, "\"=:") {
440 keyLength += 2
441 } else if strings.Contains(kname, "`") {
442 keyLength += 6
443 }
444
445 if keyLength > alignLength {
446 alignLength = keyLength
447 }
448 }
449 }
450 alignSpaces := bytes.Repeat([]byte(" "), alignLength)
451
452 KEY_LIST:
453 for _, kname := range sec.keyList {
454 key := sec.Key(kname)
455 if len(key.Comment) > 0 {
456 if len(indent) > 0 && sname != DEFAULT_SECTION {
457 buf.WriteString(indent)
458 }
459 if key.Comment[0] != '#' && key.Comment[0] != ';' {
460 key.Comment = "; " + key.Comment
461 }
462 if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
463 return 0, err
464 }
465 }
466
467 if len(indent) > 0 && sname != DEFAULT_SECTION {
468 buf.WriteString(indent)
469 }
470
471 switch {
472 case key.isAutoIncrement:
473 kname = "-"
474 case strings.ContainsAny(kname, "\"=:"):
475 kname = "`" + kname + "`"
476 case strings.Contains(kname, "`"):
477 kname = `"""` + kname + `"""`
478 }
479
480 for _, val := range key.ValueWithShadows() {
481 if _, err = buf.WriteString(kname); err != nil {
482 return 0, err
483 }
484
485 if key.isBooleanType {
486 if kname != sec.keyList[len(sec.keyList)-1] {
487 buf.WriteString(LineBreak)
488 }
489 continue KEY_LIST
490 }
491
492 // Write out alignment spaces before "=" sign
493 if PrettyFormat {
494 buf.Write(alignSpaces[:alignLength-len(kname)])
495 }
496
497 // In case key value contains "\n", "`", "\"", "#" or ";"
498 if strings.ContainsAny(val, "\n`") {
499 val = `"""` + val + `"""`
500 } else if strings.ContainsAny(val, "#;") {
501 val = "`" + val + "`"
502 }
503 if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
504 return 0, err
505 }
506 }
507 }
508
509 // Put a line between sections
510 if _, err = buf.WriteString(LineBreak); err != nil {
511 return 0, err
512 }
513 }
514
515 return buf.WriteTo(w)
516}
517
518// WriteTo writes file content into io.Writer.
519func (f *File) WriteTo(w io.Writer) (int64, error) {
520 return f.WriteToIndent(w, "")
521}
522
523// SaveToIndent writes content to file system with given value indention.
524func (f *File) SaveToIndent(filename, indent string) error {
525 // Note: Because we are truncating with os.Create,
526 // so it's safer to save to a temporary file location and rename afte done.
527 tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
528 defer os.Remove(tmpPath)
529
530 fw, err := os.Create(tmpPath)
531 if err != nil {
532 return err
533 }
534
535 if _, err = f.WriteToIndent(fw, indent); err != nil {
536 fw.Close()
537 return err
538 }
539 fw.Close()
540
541 // Remove old file and rename the new one.
542 os.Remove(filename)
543 return os.Rename(tmpPath, filename)
544}
545
546// SaveTo writes content to file system.
547func (f *File) SaveTo(filename string) error {
548 return f.SaveToIndent(filename, "")
549}
diff --git a/vendor/github.com/go-ini/ini/key.go b/vendor/github.com/go-ini/ini/key.go
deleted file mode 100644
index 852696f..0000000
--- a/vendor/github.com/go-ini/ini/key.go
+++ /dev/null
@@ -1,703 +0,0 @@
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 "strconv"
21 "strings"
22 "time"
23)
24
25// Key represents a key under a section.
26type Key struct {
27 s *Section
28 name string
29 value string
30 isAutoIncrement bool
31 isBooleanType bool
32
33 isShadow bool
34 shadows []*Key
35
36 Comment string
37}
38
39// newKey simply return a key object with given values.
40func newKey(s *Section, name, val string) *Key {
41 return &Key{
42 s: s,
43 name: name,
44 value: val,
45 }
46}
47
48func (k *Key) addShadow(val string) error {
49 if k.isShadow {
50 return errors.New("cannot add shadow to another shadow key")
51 } else if k.isAutoIncrement || k.isBooleanType {
52 return errors.New("cannot add shadow to auto-increment or boolean key")
53 }
54
55 shadow := newKey(k.s, k.name, val)
56 shadow.isShadow = true
57 k.shadows = append(k.shadows, shadow)
58 return nil
59}
60
61// AddShadow adds a new shadow key to itself.
62func (k *Key) AddShadow(val string) error {
63 if !k.s.f.options.AllowShadows {
64 return errors.New("shadow key is not allowed")
65 }
66 return k.addShadow(val)
67}
68
69// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
70type ValueMapper func(string) string
71
72// Name returns name of key.
73func (k *Key) Name() string {
74 return k.name
75}
76
77// Value returns raw value of key for performance purpose.
78func (k *Key) Value() string {
79 return k.value
80}
81
82// ValueWithShadows returns raw values of key and its shadows if any.
83func (k *Key) ValueWithShadows() []string {
84 if len(k.shadows) == 0 {
85 return []string{k.value}
86 }
87 vals := make([]string, len(k.shadows)+1)
88 vals[0] = k.value
89 for i := range k.shadows {
90 vals[i+1] = k.shadows[i].value
91 }
92 return vals
93}
94
95// transformValue takes a raw value and transforms to its final string.
96func (k *Key) transformValue(val string) string {
97 if k.s.f.ValueMapper != nil {
98 val = k.s.f.ValueMapper(val)
99 }
100
101 // Fail-fast if no indicate char found for recursive value
102 if !strings.Contains(val, "%") {
103 return val
104 }
105 for i := 0; i < _DEPTH_VALUES; i++ {
106 vr := varPattern.FindString(val)
107 if len(vr) == 0 {
108 break
109 }
110
111 // Take off leading '%(' and trailing ')s'.
112 noption := strings.TrimLeft(vr, "%(")
113 noption = strings.TrimRight(noption, ")s")
114
115 // Search in the same section.
116 nk, err := k.s.GetKey(noption)
117 if err != nil {
118 // Search again in default section.
119 nk, _ = k.s.f.Section("").GetKey(noption)
120 }
121
122 // Substitute by new value and take off leading '%(' and trailing ')s'.
123 val = strings.Replace(val, vr, nk.value, -1)
124 }
125 return val
126}
127
128// String returns string representation of value.
129func (k *Key) String() string {
130 return k.transformValue(k.value)
131}
132
133// Validate accepts a validate function which can
134// return modifed result as key value.
135func (k *Key) Validate(fn func(string) string) string {
136 return fn(k.String())
137}
138
139// parseBool returns the boolean value represented by the string.
140//
141// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
142// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
143// Any other value returns an error.
144func parseBool(str string) (value bool, err error) {
145 switch str {
146 case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
147 return true, nil
148 case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
149 return false, nil
150 }
151 return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
152}
153
154// Bool returns bool type value.
155func (k *Key) Bool() (bool, error) {
156 return parseBool(k.String())
157}
158
159// Float64 returns float64 type value.
160func (k *Key) Float64() (float64, error) {
161 return strconv.ParseFloat(k.String(), 64)
162}
163
164// Int returns int type value.
165func (k *Key) Int() (int, error) {
166 return strconv.Atoi(k.String())
167}
168
169// Int64 returns int64 type value.
170func (k *Key) Int64() (int64, error) {
171 return strconv.ParseInt(k.String(), 10, 64)
172}
173
174// Uint returns uint type valued.
175func (k *Key) Uint() (uint, error) {
176 u, e := strconv.ParseUint(k.String(), 10, 64)
177 return uint(u), e
178}
179
180// Uint64 returns uint64 type value.
181func (k *Key) Uint64() (uint64, error) {
182 return strconv.ParseUint(k.String(), 10, 64)
183}
184
185// Duration returns time.Duration type value.
186func (k *Key) Duration() (time.Duration, error) {
187 return time.ParseDuration(k.String())
188}
189
190// TimeFormat parses with given format and returns time.Time type value.
191func (k *Key) TimeFormat(format string) (time.Time, error) {
192 return time.Parse(format, k.String())
193}
194
195// Time parses with RFC3339 format and returns time.Time type value.
196func (k *Key) Time() (time.Time, error) {
197 return k.TimeFormat(time.RFC3339)
198}
199
200// MustString returns default value if key value is empty.
201func (k *Key) MustString(defaultVal string) string {
202 val := k.String()
203 if len(val) == 0 {
204 k.value = defaultVal
205 return defaultVal
206 }
207 return val
208}
209
210// MustBool always returns value without error,
211// it returns false if error occurs.
212func (k *Key) MustBool(defaultVal ...bool) bool {
213 val, err := k.Bool()
214 if len(defaultVal) > 0 && err != nil {
215 k.value = strconv.FormatBool(defaultVal[0])
216 return defaultVal[0]
217 }
218 return val
219}
220
221// MustFloat64 always returns value without error,
222// it returns 0.0 if error occurs.
223func (k *Key) MustFloat64(defaultVal ...float64) float64 {
224 val, err := k.Float64()
225 if len(defaultVal) > 0 && err != nil {
226 k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
227 return defaultVal[0]
228 }
229 return val
230}
231
232// MustInt always returns value without error,
233// it returns 0 if error occurs.
234func (k *Key) MustInt(defaultVal ...int) int {
235 val, err := k.Int()
236 if len(defaultVal) > 0 && err != nil {
237 k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
238 return defaultVal[0]
239 }
240 return val
241}
242
243// MustInt64 always returns value without error,
244// it returns 0 if error occurs.
245func (k *Key) MustInt64(defaultVal ...int64) int64 {
246 val, err := k.Int64()
247 if len(defaultVal) > 0 && err != nil {
248 k.value = strconv.FormatInt(defaultVal[0], 10)
249 return defaultVal[0]
250 }
251 return val
252}
253
254// MustUint always returns value without error,
255// it returns 0 if error occurs.
256func (k *Key) MustUint(defaultVal ...uint) uint {
257 val, err := k.Uint()
258 if len(defaultVal) > 0 && err != nil {
259 k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
260 return defaultVal[0]
261 }
262 return val
263}
264
265// MustUint64 always returns value without error,
266// it returns 0 if error occurs.
267func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
268 val, err := k.Uint64()
269 if len(defaultVal) > 0 && err != nil {
270 k.value = strconv.FormatUint(defaultVal[0], 10)
271 return defaultVal[0]
272 }
273 return val
274}
275
276// MustDuration always returns value without error,
277// it returns zero value if error occurs.
278func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
279 val, err := k.Duration()
280 if len(defaultVal) > 0 && err != nil {
281 k.value = defaultVal[0].String()
282 return defaultVal[0]
283 }
284 return val
285}
286
287// MustTimeFormat always parses with given format and returns value without error,
288// it returns zero value if error occurs.
289func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
290 val, err := k.TimeFormat(format)
291 if len(defaultVal) > 0 && err != nil {
292 k.value = defaultVal[0].Format(format)
293 return defaultVal[0]
294 }
295 return val
296}
297
298// MustTime always parses with RFC3339 format and returns value without error,
299// it returns zero value if error occurs.
300func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
301 return k.MustTimeFormat(time.RFC3339, defaultVal...)
302}
303
304// In always returns value without error,
305// it returns default value if error occurs or doesn't fit into candidates.
306func (k *Key) In(defaultVal string, candidates []string) string {
307 val := k.String()
308 for _, cand := range candidates {
309 if val == cand {
310 return val
311 }
312 }
313 return defaultVal
314}
315
316// InFloat64 always returns value without error,
317// it returns default value if error occurs or doesn't fit into candidates.
318func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
319 val := k.MustFloat64()
320 for _, cand := range candidates {
321 if val == cand {
322 return val
323 }
324 }
325 return defaultVal
326}
327
328// InInt always returns value without error,
329// it returns default value if error occurs or doesn't fit into candidates.
330func (k *Key) InInt(defaultVal int, candidates []int) int {
331 val := k.MustInt()
332 for _, cand := range candidates {
333 if val == cand {
334 return val
335 }
336 }
337 return defaultVal
338}
339
340// InInt64 always returns value without error,
341// it returns default value if error occurs or doesn't fit into candidates.
342func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
343 val := k.MustInt64()
344 for _, cand := range candidates {
345 if val == cand {
346 return val
347 }
348 }
349 return defaultVal
350}
351
352// InUint always returns value without error,
353// it returns default value if error occurs or doesn't fit into candidates.
354func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
355 val := k.MustUint()
356 for _, cand := range candidates {
357 if val == cand {
358 return val
359 }
360 }
361 return defaultVal
362}
363
364// InUint64 always returns value without error,
365// it returns default value if error occurs or doesn't fit into candidates.
366func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
367 val := k.MustUint64()
368 for _, cand := range candidates {
369 if val == cand {
370 return val
371 }
372 }
373 return defaultVal
374}
375
376// InTimeFormat always parses with given format and returns value without error,
377// it returns default value if error occurs or doesn't fit into candidates.
378func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
379 val := k.MustTimeFormat(format)
380 for _, cand := range candidates {
381 if val == cand {
382 return val
383 }
384 }
385 return defaultVal
386}
387
388// InTime always parses with RFC3339 format and returns value without error,
389// it returns default value if error occurs or doesn't fit into candidates.
390func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
391 return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
392}
393
394// RangeFloat64 checks if value is in given range inclusively,
395// and returns default value if it's not.
396func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
397 val := k.MustFloat64()
398 if val < min || val > max {
399 return defaultVal
400 }
401 return val
402}
403
404// RangeInt checks if value is in given range inclusively,
405// and returns default value if it's not.
406func (k *Key) RangeInt(defaultVal, min, max int) int {
407 val := k.MustInt()
408 if val < min || val > max {
409 return defaultVal
410 }
411 return val
412}
413
414// RangeInt64 checks if value is in given range inclusively,
415// and returns default value if it's not.
416func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
417 val := k.MustInt64()
418 if val < min || val > max {
419 return defaultVal
420 }
421 return val
422}
423
424// RangeTimeFormat checks if value with given format is in given range inclusively,
425// and returns default value if it's not.
426func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
427 val := k.MustTimeFormat(format)
428 if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
429 return defaultVal
430 }
431 return val
432}
433
434// RangeTime checks if value with RFC3339 format is in given range inclusively,
435// and returns default value if it's not.
436func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
437 return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
438}
439
440// Strings returns list of string divided by given delimiter.
441func (k *Key) Strings(delim string) []string {
442 str := k.String()
443 if len(str) == 0 {
444 return []string{}
445 }
446
447 vals := strings.Split(str, delim)
448 for i := range vals {
449 // vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
450 vals[i] = strings.TrimSpace(vals[i])
451 }
452 return vals
453}
454
455// StringsWithShadows returns list of string divided by given delimiter.
456// Shadows will also be appended if any.
457func (k *Key) StringsWithShadows(delim string) []string {
458 vals := k.ValueWithShadows()
459 results := make([]string, 0, len(vals)*2)
460 for i := range vals {
461 if len(vals) == 0 {
462 continue
463 }
464
465 results = append(results, strings.Split(vals[i], delim)...)
466 }
467
468 for i := range results {
469 results[i] = k.transformValue(strings.TrimSpace(results[i]))
470 }
471 return results
472}
473
474// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
475func (k *Key) Float64s(delim string) []float64 {
476 vals, _ := k.getFloat64s(delim, true, false)
477 return vals
478}
479
480// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
481func (k *Key) Ints(delim string) []int {
482 vals, _ := k.parseInts(k.Strings(delim), true, false)
483 return vals
484}
485
486// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
487func (k *Key) Int64s(delim string) []int64 {
488 vals, _ := k.parseInt64s(k.Strings(delim), true, false)
489 return vals
490}
491
492// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
493func (k *Key) Uints(delim string) []uint {
494 vals, _ := k.getUints(delim, true, false)
495 return vals
496}
497
498// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
499func (k *Key) Uint64s(delim string) []uint64 {
500 vals, _ := k.getUint64s(delim, true, false)
501 return vals
502}
503
504// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
505// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
506func (k *Key) TimesFormat(format, delim string) []time.Time {
507 vals, _ := k.getTimesFormat(format, delim, true, false)
508 return vals
509}
510
511// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
512// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
513func (k *Key) Times(delim string) []time.Time {
514 return k.TimesFormat(time.RFC3339, delim)
515}
516
517// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
518// it will not be included to result list.
519func (k *Key) ValidFloat64s(delim string) []float64 {
520 vals, _ := k.getFloat64s(delim, false, false)
521 return vals
522}
523
524// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
525// not be included to result list.
526func (k *Key) ValidInts(delim string) []int {
527 vals, _ := k.parseInts(k.Strings(delim), false, false)
528 return vals
529}
530
531// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
532// then it will not be included to result list.
533func (k *Key) ValidInt64s(delim string) []int64 {
534 vals, _ := k.parseInt64s(k.Strings(delim), false, false)
535 return vals
536}
537
538// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
539// then it will not be included to result list.
540func (k *Key) ValidUints(delim string) []uint {
541 vals, _ := k.getUints(delim, false, false)
542 return vals
543}
544
545// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
546// integer, then it will not be included to result list.
547func (k *Key) ValidUint64s(delim string) []uint64 {
548 vals, _ := k.getUint64s(delim, false, false)
549 return vals
550}
551
552// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
553func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
554 vals, _ := k.getTimesFormat(format, delim, false, false)
555 return vals
556}
557
558// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
559func (k *Key) ValidTimes(delim string) []time.Time {
560 return k.ValidTimesFormat(time.RFC3339, delim)
561}
562
563// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
564func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
565 return k.getFloat64s(delim, false, true)
566}
567
568// StrictInts returns list of int divided by given delimiter or error on first invalid input.
569func (k *Key) StrictInts(delim string) ([]int, error) {
570 return k.parseInts(k.Strings(delim), false, true)
571}
572
573// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
574func (k *Key) StrictInt64s(delim string) ([]int64, error) {
575 return k.parseInt64s(k.Strings(delim), false, true)
576}
577
578// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
579func (k *Key) StrictUints(delim string) ([]uint, error) {
580 return k.getUints(delim, false, true)
581}
582
583// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
584func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
585 return k.getUint64s(delim, false, true)
586}
587
588// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
589// or error on first invalid input.
590func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
591 return k.getTimesFormat(format, delim, false, true)
592}
593
594// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
595// or error on first invalid input.
596func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
597 return k.StrictTimesFormat(time.RFC3339, delim)
598}
599
600// getFloat64s returns list of float64 divided by given delimiter.
601func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]float64, error) {
602 strs := k.Strings(delim)
603 vals := make([]float64, 0, len(strs))
604 for _, str := range strs {
605 val, err := strconv.ParseFloat(str, 64)
606 if err != nil && returnOnInvalid {
607 return nil, err
608 }
609 if err == nil || addInvalid {
610 vals = append(vals, val)
611 }
612 }
613 return vals, nil
614}
615
616// parseInts transforms strings to ints.
617func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
618 vals := make([]int, 0, len(strs))
619 for _, str := range strs {
620 val, err := strconv.Atoi(str)
621 if err != nil && returnOnInvalid {
622 return nil, err
623 }
624 if err == nil || addInvalid {
625 vals = append(vals, val)
626 }
627 }
628 return vals, nil
629}
630
631// parseInt64s transforms strings to int64s.
632func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
633 vals := make([]int64, 0, len(strs))
634 for _, str := range strs {
635 val, err := strconv.ParseInt(str, 10, 64)
636 if err != nil && returnOnInvalid {
637 return nil, err
638 }
639 if err == nil || addInvalid {
640 vals = append(vals, val)
641 }
642 }
643 return vals, nil
644}
645
646// getUints returns list of uint divided by given delimiter.
647func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint, error) {
648 strs := k.Strings(delim)
649 vals := make([]uint, 0, len(strs))
650 for _, str := range strs {
651 val, err := strconv.ParseUint(str, 10, 0)
652 if err != nil && returnOnInvalid {
653 return nil, err
654 }
655 if err == nil || addInvalid {
656 vals = append(vals, uint(val))
657 }
658 }
659 return vals, nil
660}
661
662// getUint64s returns list of uint64 divided by given delimiter.
663func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
664 strs := k.Strings(delim)
665 vals := make([]uint64, 0, len(strs))
666 for _, str := range strs {
667 val, err := strconv.ParseUint(str, 10, 64)
668 if err != nil && returnOnInvalid {
669 return nil, err
670 }
671 if err == nil || addInvalid {
672 vals = append(vals, val)
673 }
674 }
675 return vals, nil
676}
677
678// getTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
679func (k *Key) getTimesFormat(format, delim string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
680 strs := k.Strings(delim)
681 vals := make([]time.Time, 0, len(strs))
682 for _, str := range strs {
683 val, err := time.Parse(format, str)
684 if err != nil && returnOnInvalid {
685 return nil, err
686 }
687 if err == nil || addInvalid {
688 vals = append(vals, val)
689 }
690 }
691 return vals, nil
692}
693
694// SetValue changes key value.
695func (k *Key) SetValue(v string) {
696 if k.s.f.BlockMode {
697 k.s.f.lock.Lock()
698 defer k.s.f.lock.Unlock()
699 }
700
701 k.value = v
702 k.s.keysHash[k.name] = v
703}
diff --git a/vendor/github.com/go-ini/ini/parser.go b/vendor/github.com/go-ini/ini/parser.go
deleted file mode 100644
index 673ef80..0000000
--- a/vendor/github.com/go-ini/ini/parser.go
+++ /dev/null
@@ -1,358 +0,0 @@
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 kname, err := p.readValue(line, f.options.IgnoreContinuation)
322 if err != nil {
323 return err
324 }
325 key, err := section.NewBooleanKey(kname)
326 if err != nil {
327 return err
328 }
329 key.Comment = strings.TrimSpace(p.comment.String())
330 p.comment.Reset()
331 continue
332 }
333 return err
334 }
335
336 // Auto increment.
337 isAutoIncr := false
338 if kname == "-" {
339 isAutoIncr = true
340 kname = "#" + strconv.Itoa(p.count)
341 p.count++
342 }
343
344 value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
345 if err != nil {
346 return err
347 }
348
349 key, err := section.NewKey(kname, value)
350 if err != nil {
351 return err
352 }
353 key.isAutoIncrement = isAutoIncr
354 key.Comment = strings.TrimSpace(p.comment.String())
355 p.comment.Reset()
356 }
357 return nil
358}
diff --git a/vendor/github.com/go-ini/ini/section.go b/vendor/github.com/go-ini/ini/section.go
deleted file mode 100644
index c9fa27e..0000000
--- a/vendor/github.com/go-ini/ini/section.go
+++ /dev/null
@@ -1,234 +0,0 @@
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 if s.f.options.AllowShadows {
72 if err := s.keys[name].addShadow(val); err != nil {
73 return nil, err
74 }
75 } else {
76 s.keys[name].value = val
77 }
78 return s.keys[name], nil
79 }
80
81 s.keyList = append(s.keyList, name)
82 s.keys[name] = newKey(s, name, val)
83 s.keysHash[name] = val
84 return s.keys[name], nil
85}
86
87// NewBooleanKey creates a new boolean type key to given section.
88func (s *Section) NewBooleanKey(name string) (*Key, error) {
89 key, err := s.NewKey(name, "true")
90 if err != nil {
91 return nil, err
92 }
93
94 key.isBooleanType = true
95 return key, nil
96}
97
98// GetKey returns key in section by given name.
99func (s *Section) GetKey(name string) (*Key, error) {
100 // FIXME: change to section level lock?
101 if s.f.BlockMode {
102 s.f.lock.RLock()
103 }
104 if s.f.options.Insensitive {
105 name = strings.ToLower(name)
106 }
107 key := s.keys[name]
108 if s.f.BlockMode {
109 s.f.lock.RUnlock()
110 }
111
112 if key == nil {
113 // Check if it is a child-section.
114 sname := s.name
115 for {
116 if i := strings.LastIndex(sname, "."); i > -1 {
117 sname = sname[:i]
118 sec, err := s.f.GetSection(sname)
119 if err != nil {
120 continue
121 }
122 return sec.GetKey(name)
123 } else {
124 break
125 }
126 }
127 return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
128 }
129 return key, nil
130}
131
132// HasKey returns true if section contains a key with given name.
133func (s *Section) HasKey(name string) bool {
134 key, _ := s.GetKey(name)
135 return key != nil
136}
137
138// Haskey is a backwards-compatible name for HasKey.
139func (s *Section) Haskey(name string) bool {
140 return s.HasKey(name)
141}
142
143// HasValue returns true if section contains given raw value.
144func (s *Section) HasValue(value string) bool {
145 if s.f.BlockMode {
146 s.f.lock.RLock()
147 defer s.f.lock.RUnlock()
148 }
149
150 for _, k := range s.keys {
151 if value == k.value {
152 return true
153 }
154 }
155 return false
156}
157
158// Key assumes named Key exists in section and returns a zero-value when not.
159func (s *Section) Key(name string) *Key {
160 key, err := s.GetKey(name)
161 if err != nil {
162 // It's OK here because the only possible error is empty key name,
163 // but if it's empty, this piece of code won't be executed.
164 key, _ = s.NewKey(name, "")
165 return key
166 }
167 return key
168}
169
170// Keys returns list of keys of section.
171func (s *Section) Keys() []*Key {
172 keys := make([]*Key, len(s.keyList))
173 for i := range s.keyList {
174 keys[i] = s.Key(s.keyList[i])
175 }
176 return keys
177}
178
179// ParentKeys returns list of keys of parent section.
180func (s *Section) ParentKeys() []*Key {
181 var parentKeys []*Key
182 sname := s.name
183 for {
184 if i := strings.LastIndex(sname, "."); i > -1 {
185 sname = sname[:i]
186 sec, err := s.f.GetSection(sname)
187 if err != nil {
188 continue
189 }
190 parentKeys = append(parentKeys, sec.Keys()...)
191 } else {
192 break
193 }
194
195 }
196 return parentKeys
197}
198
199// KeyStrings returns list of key names of section.
200func (s *Section) KeyStrings() []string {
201 list := make([]string, len(s.keyList))
202 copy(list, s.keyList)
203 return list
204}
205
206// KeysHash returns keys hash consisting of names and values.
207func (s *Section) KeysHash() map[string]string {
208 if s.f.BlockMode {
209 s.f.lock.RLock()
210 defer s.f.lock.RUnlock()
211 }
212
213 hash := map[string]string{}
214 for key, value := range s.keysHash {
215 hash[key] = value
216 }
217 return hash
218}
219
220// DeleteKey deletes a key from section.
221func (s *Section) DeleteKey(name string) {
222 if s.f.BlockMode {
223 s.f.lock.Lock()
224 defer s.f.lock.Unlock()
225 }
226
227 for i, k := range s.keyList {
228 if k == name {
229 s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
230 delete(s.keys, name)
231 return
232 }
233 }
234}
diff --git a/vendor/github.com/go-ini/ini/struct.go b/vendor/github.com/go-ini/ini/struct.go
deleted file mode 100644
index 509c682..0000000
--- a/vendor/github.com/go-ini/ini/struct.go
+++ /dev/null
@@ -1,450 +0,0 @@
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, allowShadow bool) error {
82 var strs []string
83 if allowShadow {
84 strs = key.StringsWithShadows(delim)
85 } else {
86 strs = key.Strings(delim)
87 }
88
89 numVals := len(strs)
90 if numVals == 0 {
91 return nil
92 }
93
94 var vals interface{}
95
96 sliceOf := field.Type().Elem().Kind()
97 switch sliceOf {
98 case reflect.String:
99 vals = strs
100 case reflect.Int:
101 vals, _ = key.parseInts(strs, true, false)
102 case reflect.Int64:
103 vals, _ = key.parseInt64s(strs, true, false)
104 case reflect.Uint:
105 vals = key.Uints(delim)
106 case reflect.Uint64:
107 vals = key.Uint64s(delim)
108 case reflect.Float64:
109 vals = key.Float64s(delim)
110 case reflectTime:
111 vals = key.Times(delim)
112 default:
113 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
114 }
115
116 slice := reflect.MakeSlice(field.Type(), numVals, numVals)
117 for i := 0; i < numVals; i++ {
118 switch sliceOf {
119 case reflect.String:
120 slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
121 case reflect.Int:
122 slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
123 case reflect.Int64:
124 slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
125 case reflect.Uint:
126 slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
127 case reflect.Uint64:
128 slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
129 case reflect.Float64:
130 slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
131 case reflectTime:
132 slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
133 }
134 }
135 field.Set(slice)
136 return nil
137}
138
139// setWithProperType sets proper value to field based on its type,
140// but it does not return error for failing parsing,
141// because we want to use default value that is already assigned to strcut.
142func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
143 switch t.Kind() {
144 case reflect.String:
145 if len(key.String()) == 0 {
146 return nil
147 }
148 field.SetString(key.String())
149 case reflect.Bool:
150 boolVal, err := key.Bool()
151 if err != nil {
152 return nil
153 }
154 field.SetBool(boolVal)
155 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
156 durationVal, err := key.Duration()
157 // Skip zero value
158 if err == nil && int(durationVal) > 0 {
159 field.Set(reflect.ValueOf(durationVal))
160 return nil
161 }
162
163 intVal, err := key.Int64()
164 if err != nil || intVal == 0 {
165 return nil
166 }
167 field.SetInt(intVal)
168 // byte is an alias for uint8, so supporting uint8 breaks support for byte
169 case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
170 durationVal, err := key.Duration()
171 // Skip zero value
172 if err == nil && int(durationVal) > 0 {
173 field.Set(reflect.ValueOf(durationVal))
174 return nil
175 }
176
177 uintVal, err := key.Uint64()
178 if err != nil {
179 return nil
180 }
181 field.SetUint(uintVal)
182
183 case reflect.Float32, reflect.Float64:
184 floatVal, err := key.Float64()
185 if err != nil {
186 return nil
187 }
188 field.SetFloat(floatVal)
189 case reflectTime:
190 timeVal, err := key.Time()
191 if err != nil {
192 return nil
193 }
194 field.Set(reflect.ValueOf(timeVal))
195 case reflect.Slice:
196 return setSliceWithProperType(key, field, delim, allowShadow)
197 default:
198 return fmt.Errorf("unsupported type '%s'", t)
199 }
200 return nil
201}
202
203func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
204 opts := strings.SplitN(tag, ",", 3)
205 rawName = opts[0]
206 if len(opts) > 1 {
207 omitEmpty = opts[1] == "omitempty"
208 }
209 if len(opts) > 2 {
210 allowShadow = opts[2] == "allowshadow"
211 }
212 return rawName, omitEmpty, allowShadow
213}
214
215func (s *Section) mapTo(val reflect.Value) error {
216 if val.Kind() == reflect.Ptr {
217 val = val.Elem()
218 }
219 typ := val.Type()
220
221 for i := 0; i < typ.NumField(); i++ {
222 field := val.Field(i)
223 tpField := typ.Field(i)
224
225 tag := tpField.Tag.Get("ini")
226 if tag == "-" {
227 continue
228 }
229
230 rawName, _, allowShadow := parseTagOptions(tag)
231 fieldName := s.parseFieldName(tpField.Name, rawName)
232 if len(fieldName) == 0 || !field.CanSet() {
233 continue
234 }
235
236 isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
237 isStruct := tpField.Type.Kind() == reflect.Struct
238 if isAnonymous {
239 field.Set(reflect.New(tpField.Type.Elem()))
240 }
241
242 if isAnonymous || isStruct {
243 if sec, err := s.f.GetSection(fieldName); err == nil {
244 if err = sec.mapTo(field); err != nil {
245 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
246 }
247 continue
248 }
249 }
250
251 if key, err := s.GetKey(fieldName); err == nil {
252 delim := parseDelim(tpField.Tag.Get("delim"))
253 if err = setWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
254 return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
255 }
256 }
257 }
258 return nil
259}
260
261// MapTo maps section to given struct.
262func (s *Section) MapTo(v interface{}) error {
263 typ := reflect.TypeOf(v)
264 val := reflect.ValueOf(v)
265 if typ.Kind() == reflect.Ptr {
266 typ = typ.Elem()
267 val = val.Elem()
268 } else {
269 return errors.New("cannot map to non-pointer struct")
270 }
271
272 return s.mapTo(val)
273}
274
275// MapTo maps file to given struct.
276func (f *File) MapTo(v interface{}) error {
277 return f.Section("").MapTo(v)
278}
279
280// MapTo maps data sources to given struct with name mapper.
281func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
282 cfg, err := Load(source, others...)
283 if err != nil {
284 return err
285 }
286 cfg.NameMapper = mapper
287 return cfg.MapTo(v)
288}
289
290// MapTo maps data sources to given struct.
291func MapTo(v, source interface{}, others ...interface{}) error {
292 return MapToWithMapper(v, nil, source, others...)
293}
294
295// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
296func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
297 slice := field.Slice(0, field.Len())
298 if field.Len() == 0 {
299 return nil
300 }
301
302 var buf bytes.Buffer
303 sliceOf := field.Type().Elem().Kind()
304 for i := 0; i < field.Len(); i++ {
305 switch sliceOf {
306 case reflect.String:
307 buf.WriteString(slice.Index(i).String())
308 case reflect.Int, reflect.Int64:
309 buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
310 case reflect.Uint, reflect.Uint64:
311 buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
312 case reflect.Float64:
313 buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
314 case reflectTime:
315 buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
316 default:
317 return fmt.Errorf("unsupported type '[]%s'", sliceOf)
318 }
319 buf.WriteString(delim)
320 }
321 key.SetValue(buf.String()[:buf.Len()-1])
322 return nil
323}
324
325// reflectWithProperType does the opposite thing as setWithProperType.
326func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
327 switch t.Kind() {
328 case reflect.String:
329 key.SetValue(field.String())
330 case reflect.Bool:
331 key.SetValue(fmt.Sprint(field.Bool()))
332 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
333 key.SetValue(fmt.Sprint(field.Int()))
334 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
335 key.SetValue(fmt.Sprint(field.Uint()))
336 case reflect.Float32, reflect.Float64:
337 key.SetValue(fmt.Sprint(field.Float()))
338 case reflectTime:
339 key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
340 case reflect.Slice:
341 return reflectSliceWithProperType(key, field, delim)
342 default:
343 return fmt.Errorf("unsupported type '%s'", t)
344 }
345 return nil
346}
347
348// CR: copied from encoding/json/encode.go with modifications of time.Time support.
349// TODO: add more test coverage.
350func isEmptyValue(v reflect.Value) bool {
351 switch v.Kind() {
352 case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
353 return v.Len() == 0
354 case reflect.Bool:
355 return !v.Bool()
356 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
357 return v.Int() == 0
358 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
359 return v.Uint() == 0
360 case reflect.Float32, reflect.Float64:
361 return v.Float() == 0
362 case reflectTime:
363 return v.Interface().(time.Time).IsZero()
364 case reflect.Interface, reflect.Ptr:
365 return v.IsNil()
366 }
367 return false
368}
369
370func (s *Section) reflectFrom(val reflect.Value) error {
371 if val.Kind() == reflect.Ptr {
372 val = val.Elem()
373 }
374 typ := val.Type()
375
376 for i := 0; i < typ.NumField(); i++ {
377 field := val.Field(i)
378 tpField := typ.Field(i)
379
380 tag := tpField.Tag.Get("ini")
381 if tag == "-" {
382 continue
383 }
384
385 opts := strings.SplitN(tag, ",", 2)
386 if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
387 continue
388 }
389
390 fieldName := s.parseFieldName(tpField.Name, opts[0])
391 if len(fieldName) == 0 || !field.CanSet() {
392 continue
393 }
394
395 if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
396 (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
397 // Note: The only error here is section doesn't exist.
398 sec, err := s.f.GetSection(fieldName)
399 if err != nil {
400 // Note: fieldName can never be empty here, ignore error.
401 sec, _ = s.f.NewSection(fieldName)
402 }
403 if err = sec.reflectFrom(field); err != nil {
404 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
405 }
406 continue
407 }
408
409 // Note: Same reason as secion.
410 key, err := s.GetKey(fieldName)
411 if err != nil {
412 key, _ = s.NewKey(fieldName, "")
413 }
414 if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
415 return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
416 }
417
418 }
419 return nil
420}
421
422// ReflectFrom reflects secion from given struct.
423func (s *Section) ReflectFrom(v interface{}) error {
424 typ := reflect.TypeOf(v)
425 val := reflect.ValueOf(v)
426 if typ.Kind() == reflect.Ptr {
427 typ = typ.Elem()
428 val = val.Elem()
429 } else {
430 return errors.New("cannot reflect from non-pointer struct")
431 }
432
433 return s.reflectFrom(val)
434}
435
436// ReflectFrom reflects file from given struct.
437func (f *File) ReflectFrom(v interface{}) error {
438 return f.Section("").ReflectFrom(v)
439}
440
441// ReflectFrom reflects data sources from given struct with name mapper.
442func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
443 cfg.NameMapper = mapper
444 return cfg.ReflectFrom(v)
445}
446
447// ReflectFrom reflects data sources from given struct.
448func ReflectFrom(cfg *File, v interface{}) error {
449 return ReflectFromWithMapper(cfg, v, nil)
450}