diff options
Diffstat (limited to 'vendor/github.com/go-ini')
-rw-r--r-- | vendor/github.com/go-ini/ini/.gitignore | 5 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/.travis.yml | 17 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/LICENSE | 191 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/Makefile | 12 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/README.md | 740 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/README_ZH.md | 727 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/error.go | 32 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/ini.go | 549 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/key.go | 703 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/parser.go | 358 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/section.go | 234 | ||||
-rw-r--r-- | vendor/github.com/go-ini/ini/struct.go | 450 |
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 @@ | |||
1 | testdata/conf_out.ini | ||
2 | ini.sublime-project | ||
3 | ini.sublime-workspace | ||
4 | testdata/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 @@ | |||
1 | sudo: false | ||
2 | language: go | ||
3 | go: | ||
4 | - 1.4.x | ||
5 | - 1.5.x | ||
6 | - 1.6.x | ||
7 | - 1.7.x | ||
8 | - master | ||
9 | |||
10 | script: | ||
11 | - go get golang.org/x/tools/cmd/cover | ||
12 | - go get github.com/smartystreets/goconvey | ||
13 | - go test -v -cover -race | ||
14 | |||
15 | notifications: | ||
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 @@ | |||
1 | Apache License | ||
2 | Version 2.0, January 2004 | ||
3 | http://www.apache.org/licenses/ | ||
4 | |||
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
6 | |||
7 | 1. Definitions. | ||
8 | |||
9 | "License" shall mean the terms and conditions for use, reproduction, and | ||
10 | distribution as defined by Sections 1 through 9 of this document. | ||
11 | |||
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright | ||
13 | owner that is granting the License. | ||
14 | |||
15 | "Legal Entity" shall mean the union of the acting entity and all other entities | ||
16 | that control, are controlled by, or are under common control with that entity. | ||
17 | For the purposes of this definition, "control" means (i) the power, direct or | ||
18 | indirect, to cause the direction or management of such entity, whether by | ||
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
20 | outstanding shares, or (iii) beneficial ownership of such entity. | ||
21 | |||
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising | ||
23 | permissions granted by this License. | ||
24 | |||
25 | "Source" form shall mean the preferred form for making modifications, including | ||
26 | but not limited to software source code, documentation source, and configuration | ||
27 | files. | ||
28 | |||
29 | "Object" form shall mean any form resulting from mechanical transformation or | ||
30 | translation of a Source form, including but not limited to compiled object code, | ||
31 | generated documentation, and conversions to other media types. | ||
32 | |||
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made | ||
34 | available under the License, as indicated by a copyright notice that is included | ||
35 | in 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 | ||
38 | is based on (or derived from) the Work and for which the editorial revisions, | ||
39 | annotations, elaborations, or other modifications represent, as a whole, an | ||
40 | original work of authorship. For the purposes of this License, Derivative Works | ||
41 | shall not include works that remain separable from, or merely link (or bind by | ||
42 | name) to the interfaces of, the Work and Derivative Works thereof. | ||
43 | |||
44 | "Contribution" shall mean any work of authorship, including the original version | ||
45 | of the Work and any modifications or additions to that Work or Derivative Works | ||
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work | ||
47 | by the copyright owner or by an individual or Legal Entity authorized to submit | ||
48 | on behalf of the copyright owner. For the purposes of this definition, | ||
49 | "submitted" means any form of electronic, verbal, or written communication sent | ||
50 | to the Licensor or its representatives, including but not limited to | ||
51 | communication on electronic mailing lists, source code control systems, and | ||
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for | ||
53 | the purpose of discussing and improving the Work, but excluding communication | ||
54 | that is conspicuously marked or otherwise designated in writing by the copyright | ||
55 | owner as "Not a Contribution." | ||
56 | |||
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf | ||
58 | of whom a Contribution has been received by Licensor and subsequently | ||
59 | incorporated within the Work. | ||
60 | |||
61 | 2. Grant of Copyright License. | ||
62 | |||
63 | Subject to the terms and conditions of this License, each Contributor hereby | ||
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, | ||
65 | irrevocable copyright license to reproduce, prepare Derivative Works of, | ||
66 | publicly display, publicly perform, sublicense, and distribute the Work and such | ||
67 | Derivative Works in Source or Object form. | ||
68 | |||
69 | 3. Grant of Patent License. | ||
70 | |||
71 | Subject to the terms and conditions of this License, each Contributor hereby | ||
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, | ||
73 | irrevocable (except as stated in this section) patent license to make, have | ||
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where | ||
75 | such license applies only to those patent claims licensable by such Contributor | ||
76 | that are necessarily infringed by their Contribution(s) alone or by combination | ||
77 | of their Contribution(s) with the Work to which such Contribution(s) was | ||
78 | submitted. If You institute patent litigation against any entity (including a | ||
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a | ||
80 | Contribution incorporated within the Work constitutes direct or contributory | ||
81 | patent infringement, then any patent licenses granted to You under this License | ||
82 | for that Work shall terminate as of the date such litigation is filed. | ||
83 | |||
84 | 4. Redistribution. | ||
85 | |||
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof | ||
87 | in any medium, with or without modifications, and in Source or Object form, | ||
88 | provided that You meet the following conditions: | ||
89 | |||
90 | You must give any other recipients of the Work or Derivative Works a copy of | ||
91 | this License; and | ||
92 | You must cause any modified files to carry prominent notices stating that You | ||
93 | changed the files; and | ||
94 | You must retain, in the Source form of any Derivative Works that You distribute, | ||
95 | all copyright, patent, trademark, and attribution notices from the Source form | ||
96 | of the Work, excluding those notices that do not pertain to any part of the | ||
97 | Derivative Works; and | ||
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any | ||
99 | Derivative Works that You distribute must include a readable copy of the | ||
100 | attribution notices contained within such NOTICE file, excluding those notices | ||
101 | that do not pertain to any part of the Derivative Works, in at least one of the | ||
102 | following places: within a NOTICE text file distributed as part of the | ||
103 | Derivative Works; within the Source form or documentation, if provided along | ||
104 | with the Derivative Works; or, within a display generated by the Derivative | ||
105 | Works, if and wherever such third-party notices normally appear. The contents of | ||
106 | the NOTICE file are for informational purposes only and do not modify the | ||
107 | License. You may add Your own attribution notices within Derivative Works that | ||
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, | ||
109 | provided that such additional attribution notices cannot be construed as | ||
110 | modifying the License. | ||
111 | You may add Your own copyright statement to Your modifications and may provide | ||
112 | additional or different license terms and conditions for use, reproduction, or | ||
113 | distribution of Your modifications, or for any such Derivative Works as a whole, | ||
114 | provided Your use, reproduction, and distribution of the Work otherwise complies | ||
115 | with the conditions stated in this License. | ||
116 | |||
117 | 5. Submission of Contributions. | ||
118 | |||
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted | ||
120 | for inclusion in the Work by You to the Licensor shall be under the terms and | ||
121 | conditions of this License, without any additional terms or conditions. | ||
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of | ||
123 | any separate license agreement you may have executed with Licensor regarding | ||
124 | such Contributions. | ||
125 | |||
126 | 6. Trademarks. | ||
127 | |||
128 | This License does not grant permission to use the trade names, trademarks, | ||
129 | service marks, or product names of the Licensor, except as required for | ||
130 | reasonable and customary use in describing the origin of the Work and | ||
131 | reproducing the content of the NOTICE file. | ||
132 | |||
133 | 7. Disclaimer of Warranty. | ||
134 | |||
135 | Unless required by applicable law or agreed to in writing, Licensor provides the | ||
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, | ||
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, | ||
138 | including, without limitation, any warranties or conditions of TITLE, | ||
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are | ||
140 | solely responsible for determining the appropriateness of using or | ||
141 | redistributing the Work and assume any risks associated with Your exercise of | ||
142 | permissions under this License. | ||
143 | |||
144 | 8. Limitation of Liability. | ||
145 | |||
146 | In no event and under no legal theory, whether in tort (including negligence), | ||
147 | contract, or otherwise, unless required by applicable law (such as deliberate | ||
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be | ||
149 | liable to You for damages, including any direct, indirect, special, incidental, | ||
150 | or consequential damages of any character arising as a result of this License or | ||
151 | out of the use or inability to use the Work (including but not limited to | ||
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or | ||
153 | any and all other commercial damages or losses), even if such Contributor has | ||
154 | been advised of the possibility of such damages. | ||
155 | |||
156 | 9. Accepting Warranty or Additional Liability. | ||
157 | |||
158 | While redistributing the Work or Derivative Works thereof, You may choose to | ||
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or | ||
160 | other liability obligations and/or rights consistent with this License. However, | ||
161 | in accepting such obligations, You may act only on Your own behalf and on Your | ||
162 | sole responsibility, not on behalf of any other Contributor, and only if You | ||
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability | ||
164 | incurred by, or claims asserted against, such Contributor by reason of your | ||
165 | accepting any such warranty or additional liability. | ||
166 | |||
167 | END OF TERMS AND CONDITIONS | ||
168 | |||
169 | APPENDIX: How to apply the Apache License to your work | ||
170 | |||
171 | To apply the Apache License to your work, attach the following boilerplate | ||
172 | notice, with the fields enclosed by brackets "[]" replaced with your own | ||
173 | identifying information. (Don't include the brackets!) The text should be | ||
174 | enclosed in the appropriate comment syntax for the file format. We also | ||
175 | recommend that a file or class name and description of purpose be included on | ||
176 | the same "printed page" as the copyright notice for easier identification within | ||
177 | third-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 | |||
3 | build: vet bench | ||
4 | |||
5 | test: | ||
6 | go test -v -cover -race | ||
7 | |||
8 | bench: | ||
9 | go test -v -cover -race -test.bench=. -test.benchmem | ||
10 | |||
11 | vet: | ||
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 @@ | |||
1 | INI [![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 | |||
6 | Package 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 | |||
25 | To use a tagged revision: | ||
26 | |||
27 | go get gopkg.in/ini.v1 | ||
28 | |||
29 | To use with latest changes: | ||
30 | |||
31 | go get github.com/go-ini/ini | ||
32 | |||
33 | Please add `-u` flag to update in the future. | ||
34 | |||
35 | ### Testing | ||
36 | |||
37 | If you want to test on your machine, please apply `-t` flag: | ||
38 | |||
39 | go get -t gopkg.in/ini.v1 | ||
40 | |||
41 | Please add `-u` flag to update in the future. | ||
42 | |||
43 | ## Getting Started | ||
44 | |||
45 | ### Loading from data sources | ||
46 | |||
47 | A **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 | ||
50 | cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data")))) | ||
51 | ``` | ||
52 | |||
53 | Or start with an empty object: | ||
54 | |||
55 | ```go | ||
56 | cfg := ini.Empty() | ||
57 | ``` | ||
58 | |||
59 | When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later. | ||
60 | |||
61 | ```go | ||
62 | err := cfg.Append("other file", []byte("other raw data")) | ||
63 | ``` | ||
64 | |||
65 | If 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 | ||
68 | cfg, err := ini.LooseLoad("filename", "filename_404") | ||
69 | ``` | ||
70 | |||
71 | The 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 | |||
75 | When 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 | ||
78 | cfg, err := ini.InsensitiveLoad("filename") | ||
79 | //... | ||
80 | |||
81 | // sec1 and sec2 are the exactly same section object | ||
82 | sec1, err := cfg.GetSection("Section") | ||
83 | sec2, err := cfg.GetSection("SecTIOn") | ||
84 | |||
85 | // key1 and key2 are the exactly same key object | ||
86 | key1, err := cfg.GetKey("Key") | ||
87 | key2, err := cfg.GetKey("KeY") | ||
88 | ``` | ||
89 | |||
90 | #### MySQL-like boolean key | ||
91 | |||
92 | MySQL's configuration allows a key without value as follows: | ||
93 | |||
94 | ```ini | ||
95 | [mysqld] | ||
96 | ... | ||
97 | skip-host-cache | ||
98 | skip-name-resolve | ||
99 | ``` | ||
100 | |||
101 | By 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 | ||
104 | cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf")) | ||
105 | ``` | ||
106 | |||
107 | The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read. | ||
108 | |||
109 | To generate such keys in your program, you could use `NewBooleanKey`: | ||
110 | |||
111 | ```go | ||
112 | key, err := sec.NewBooleanKey("skip-host-cache") | ||
113 | ``` | ||
114 | |||
115 | #### Comment | ||
116 | |||
117 | Take care that following format will be treated as comment: | ||
118 | |||
119 | 1. Line begins with `#` or `;` | ||
120 | 2. Words after `#` or `;` | ||
121 | 3. Words after section name (i.e words after `[some section name]`) | ||
122 | |||
123 | If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```. | ||
124 | |||
125 | ### Working with sections | ||
126 | |||
127 | To get a section, you would need to: | ||
128 | |||
129 | ```go | ||
130 | section, err := cfg.GetSection("section name") | ||
131 | ``` | ||
132 | |||
133 | For a shortcut for default section, just give an empty string as name: | ||
134 | |||
135 | ```go | ||
136 | section, err := cfg.GetSection("") | ||
137 | ``` | ||
138 | |||
139 | When you're pretty sure the section exists, following code could make your life easier: | ||
140 | |||
141 | ```go | ||
142 | section := cfg.Section("section name") | ||
143 | ``` | ||
144 | |||
145 | What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you. | ||
146 | |||
147 | To create a new section: | ||
148 | |||
149 | ```go | ||
150 | err := cfg.NewSection("new section") | ||
151 | ``` | ||
152 | |||
153 | To get a list of sections or section names: | ||
154 | |||
155 | ```go | ||
156 | sections := cfg.Sections() | ||
157 | names := cfg.SectionStrings() | ||
158 | ``` | ||
159 | |||
160 | ### Working with keys | ||
161 | |||
162 | To get a key under a section: | ||
163 | |||
164 | ```go | ||
165 | key, err := cfg.Section("").GetKey("key name") | ||
166 | ``` | ||
167 | |||
168 | Same rule applies to key operations: | ||
169 | |||
170 | ```go | ||
171 | key := cfg.Section("").Key("key name") | ||
172 | ``` | ||
173 | |||
174 | To check if a key exists: | ||
175 | |||
176 | ```go | ||
177 | yes := cfg.Section("").HasKey("key name") | ||
178 | ``` | ||
179 | |||
180 | To create a new key: | ||
181 | |||
182 | ```go | ||
183 | err := cfg.Section("").NewKey("name", "value") | ||
184 | ``` | ||
185 | |||
186 | To get a list of keys or key names: | ||
187 | |||
188 | ```go | ||
189 | keys := cfg.Section("").Keys() | ||
190 | names := cfg.Section("").KeyStrings() | ||
191 | ``` | ||
192 | |||
193 | To get a clone hash of keys and corresponding values: | ||
194 | |||
195 | ```go | ||
196 | hash := cfg.Section("").KeysHash() | ||
197 | ``` | ||
198 | |||
199 | ### Working with values | ||
200 | |||
201 | To get a string value: | ||
202 | |||
203 | ```go | ||
204 | val := cfg.Section("").Key("key name").String() | ||
205 | ``` | ||
206 | |||
207 | To validate key value on the fly: | ||
208 | |||
209 | ```go | ||
210 | val := 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 | |||
218 | If 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 | ||
221 | val := cfg.Section("").Key("key name").Value() | ||
222 | ``` | ||
223 | |||
224 | To check if raw value exists: | ||
225 | |||
226 | ```go | ||
227 | yes := cfg.Section("").HasValue("test value") | ||
228 | ``` | ||
229 | |||
230 | To 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 | ||
236 | v, err = cfg.Section("").Key("BOOL").Bool() | ||
237 | v, err = cfg.Section("").Key("FLOAT64").Float64() | ||
238 | v, err = cfg.Section("").Key("INT").Int() | ||
239 | v, err = cfg.Section("").Key("INT64").Int64() | ||
240 | v, err = cfg.Section("").Key("UINT").Uint() | ||
241 | v, err = cfg.Section("").Key("UINT64").Uint64() | ||
242 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339) | ||
243 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339 | ||
244 | |||
245 | v = cfg.Section("").Key("BOOL").MustBool() | ||
246 | v = cfg.Section("").Key("FLOAT64").MustFloat64() | ||
247 | v = cfg.Section("").Key("INT").MustInt() | ||
248 | v = cfg.Section("").Key("INT64").MustInt64() | ||
249 | v = cfg.Section("").Key("UINT").MustUint() | ||
250 | v = cfg.Section("").Key("UINT64").MustUint64() | ||
251 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339) | ||
252 | v = 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 | |||
258 | v = cfg.Section("").Key("String").MustString("default") | ||
259 | v = cfg.Section("").Key("BOOL").MustBool(true) | ||
260 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25) | ||
261 | v = cfg.Section("").Key("INT").MustInt(10) | ||
262 | v = cfg.Section("").Key("INT64").MustInt64(99) | ||
263 | v = cfg.Section("").Key("UINT").MustUint(3) | ||
264 | v = cfg.Section("").Key("UINT64").MustUint64(6) | ||
265 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now()) | ||
266 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339 | ||
267 | ``` | ||
268 | |||
269 | What if my value is three-line long? | ||
270 | |||
271 | ```ini | ||
272 | [advance] | ||
273 | ADDRESS = """404 road, | ||
274 | NotFound, State, 5000 | ||
275 | Earth""" | ||
276 | ``` | ||
277 | |||
278 | Not a problem! | ||
279 | |||
280 | ```go | ||
281 | cfg.Section("advance").Key("ADDRESS").String() | ||
282 | |||
283 | /* --- start --- | ||
284 | 404 road, | ||
285 | NotFound, State, 5000 | ||
286 | Earth | ||
287 | ------ end --- */ | ||
288 | ``` | ||
289 | |||
290 | That's cool, how about continuation lines? | ||
291 | |||
292 | ```ini | ||
293 | [advance] | ||
294 | two_lines = how about \ | ||
295 | continuation lines? | ||
296 | lots_of_lines = 1 \ | ||
297 | 2 \ | ||
298 | 3 \ | ||
299 | 4 | ||
300 | ``` | ||
301 | |||
302 | Piece of cake! | ||
303 | |||
304 | ```go | ||
305 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines? | ||
306 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4 | ||
307 | ``` | ||
308 | |||
309 | Well, I hate continuation lines, how do I disable that? | ||
310 | |||
311 | ```go | ||
312 | cfg, err := ini.LoadSources(ini.LoadOptions{ | ||
313 | IgnoreContinuation: true, | ||
314 | }, "filename") | ||
315 | ``` | ||
316 | |||
317 | Holy crap! | ||
318 | |||
319 | Note that single quotes around values will be stripped: | ||
320 | |||
321 | ```ini | ||
322 | foo = "some value" // foo: some value | ||
323 | bar = 'some value' // bar: some value | ||
324 | ``` | ||
325 | |||
326 | That's all? Hmm, no. | ||
327 | |||
328 | #### Helper methods of working with values | ||
329 | |||
330 | To get value with given candidates: | ||
331 | |||
332 | ```go | ||
333 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"}) | ||
334 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75}) | ||
335 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30}) | ||
336 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30}) | ||
337 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9}) | ||
338 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9}) | ||
339 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3}) | ||
340 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339 | ||
341 | ``` | ||
342 | |||
343 | Default 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 | |||
345 | To validate value in a given range: | ||
346 | |||
347 | ```go | ||
348 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2) | ||
349 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20) | ||
350 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20) | ||
351 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9) | ||
352 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9) | ||
353 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime) | ||
354 | vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339 | ||
355 | ``` | ||
356 | |||
357 | ##### Auto-split values into a slice | ||
358 | |||
359 | To 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] | ||
364 | vals = cfg.Section("").Key("STRINGS").Strings(",") | ||
365 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",") | ||
366 | vals = cfg.Section("").Key("INTS").Ints(",") | ||
367 | vals = cfg.Section("").Key("INT64S").Int64s(",") | ||
368 | vals = cfg.Section("").Key("UINTS").Uints(",") | ||
369 | vals = cfg.Section("").Key("UINT64S").Uint64s(",") | ||
370 | vals = cfg.Section("").Key("TIMES").Times(",") | ||
371 | ``` | ||
372 | |||
373 | To 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] | ||
378 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",") | ||
379 | vals = cfg.Section("").Key("INTS").ValidInts(",") | ||
380 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",") | ||
381 | vals = cfg.Section("").Key("UINTS").ValidUints(",") | ||
382 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",") | ||
383 | vals = cfg.Section("").Key("TIMES").ValidTimes(",") | ||
384 | ``` | ||
385 | |||
386 | Or 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 | ||
391 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",") | ||
392 | vals = cfg.Section("").Key("INTS").StrictInts(",") | ||
393 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",") | ||
394 | vals = cfg.Section("").Key("UINTS").StrictUints(",") | ||
395 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",") | ||
396 | vals = cfg.Section("").Key("TIMES").StrictTimes(",") | ||
397 | ``` | ||
398 | |||
399 | ### Save your configuration | ||
400 | |||
401 | Finally, it's time to save your configuration to somewhere. | ||
402 | |||
403 | A typical way to save configuration is writing it to a file: | ||
404 | |||
405 | ```go | ||
406 | // ... | ||
407 | err = cfg.SaveTo("my.ini") | ||
408 | err = cfg.SaveToIndent("my.ini", "\t") | ||
409 | ``` | ||
410 | |||
411 | Another way to save is writing to a `io.Writer` interface: | ||
412 | |||
413 | ```go | ||
414 | // ... | ||
415 | cfg.WriteTo(writer) | ||
416 | cfg.WriteToIndent(writer, "\t") | ||
417 | ``` | ||
418 | |||
419 | By default, spaces are used to align "=" sign between key and values, to disable that: | ||
420 | |||
421 | ```go | ||
422 | ini.PrettyFormat = false | ||
423 | ``` | ||
424 | |||
425 | ## Advanced Usage | ||
426 | |||
427 | ### Recursive Values | ||
428 | |||
429 | For 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 | ||
432 | NAME = ini | ||
433 | |||
434 | [author] | ||
435 | NAME = Unknwon | ||
436 | GITHUB = https://github.com/%(NAME)s | ||
437 | |||
438 | [package] | ||
439 | FULL_NAME = github.com/go-ini/%(NAME)s | ||
440 | ``` | ||
441 | |||
442 | ```go | ||
443 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon | ||
444 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini | ||
445 | ``` | ||
446 | |||
447 | ### Parent-child Sections | ||
448 | |||
449 | You 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 | ||
452 | NAME = ini | ||
453 | VERSION = v1 | ||
454 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s | ||
455 | |||
456 | [package] | ||
457 | CLONE_URL = https://%(IMPORT_PATH)s | ||
458 | |||
459 | [package.sub] | ||
460 | ``` | ||
461 | |||
462 | ```go | ||
463 | cfg.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 | ||
469 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] | ||
470 | ``` | ||
471 | |||
472 | ### Unparseable Sections | ||
473 | |||
474 | Sometimes, 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 | ||
477 | cfg, 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 | |||
480 | body := 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 | |||
489 | If 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 | ||
499 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"} | ||
500 | ``` | ||
501 | |||
502 | ### Map To Struct | ||
503 | |||
504 | Want more objective way to play with INI? Cool. | ||
505 | |||
506 | ```ini | ||
507 | Name = Unknwon | ||
508 | age = 21 | ||
509 | Male = true | ||
510 | Born = 1993-01-01T20:17:05Z | ||
511 | |||
512 | [Note] | ||
513 | Content = Hi is a good man! | ||
514 | Cities = HangZhou, Boston | ||
515 | ``` | ||
516 | |||
517 | ```go | ||
518 | type Note struct { | ||
519 | Content string | ||
520 | Cities []string | ||
521 | } | ||
522 | |||
523 | type 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 | |||
532 | func 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 | |||
550 | Can I have default value for field? Absolutely. | ||
551 | |||
552 | Assign 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 | // ... | ||
556 | p := &Person{ | ||
557 | Name: "Joe", | ||
558 | } | ||
559 | // ... | ||
560 | ``` | ||
561 | |||
562 | It'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 | |||
566 | Why not? | ||
567 | |||
568 | ```go | ||
569 | type Embeded struct { | ||
570 | Dates []time.Time `delim:"|"` | ||
571 | Places []string `ini:"places,omitempty"` | ||
572 | None []int `ini:",omitempty"` | ||
573 | } | ||
574 | |||
575 | type Author struct { | ||
576 | Name string `ini:"NAME"` | ||
577 | Male bool | ||
578 | Age int | ||
579 | GPA float64 | ||
580 | NeverMind string `ini:"-"` | ||
581 | *Embeded | ||
582 | } | ||
583 | |||
584 | func 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 | |||
597 | So, what do I get? | ||
598 | |||
599 | ```ini | ||
600 | NAME = Unknwon | ||
601 | Male = true | ||
602 | Age = 21 | ||
603 | GPA = 2.8 | ||
604 | |||
605 | [Embeded] | ||
606 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00 | ||
607 | places = HangZhou,Boston | ||
608 | ``` | ||
609 | |||
610 | #### Name Mapper | ||
611 | |||
612 | To 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 | |||
614 | There 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 | |||
619 | To use them: | ||
620 | |||
621 | ```go | ||
622 | type Info struct { | ||
623 | PackageName string | ||
624 | } | ||
625 | |||
626 | func 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 | |||
639 | Same rules of name mapper apply to `ini.ReflectFromWithMapper` function. | ||
640 | |||
641 | #### Value Mapper | ||
642 | |||
643 | To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values: | ||
644 | |||
645 | ```go | ||
646 | type Env struct { | ||
647 | Foo string `ini:"foo"` | ||
648 | } | ||
649 | |||
650 | func 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 | |||
659 | This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`. | ||
660 | |||
661 | #### Other Notes On Map/Reflect | ||
662 | |||
663 | Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature: | ||
664 | |||
665 | ```go | ||
666 | type Child struct { | ||
667 | Age string | ||
668 | } | ||
669 | |||
670 | type Parent struct { | ||
671 | Name string | ||
672 | Child | ||
673 | } | ||
674 | |||
675 | type Config struct { | ||
676 | City string | ||
677 | Parent | ||
678 | } | ||
679 | ``` | ||
680 | |||
681 | Example configuration: | ||
682 | |||
683 | ```ini | ||
684 | City = Boston | ||
685 | |||
686 | [Parent] | ||
687 | Name = Unknwon | ||
688 | |||
689 | [Child] | ||
690 | Age = 21 | ||
691 | ``` | ||
692 | |||
693 | What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome. | ||
694 | |||
695 | ```go | ||
696 | type Child struct { | ||
697 | Age string | ||
698 | } | ||
699 | |||
700 | type Parent struct { | ||
701 | Name string | ||
702 | Child `ini:"Parent"` | ||
703 | } | ||
704 | |||
705 | type Config struct { | ||
706 | City string | ||
707 | Parent | ||
708 | } | ||
709 | ``` | ||
710 | |||
711 | Example configuration: | ||
712 | |||
713 | ```ini | ||
714 | City = Boston | ||
715 | |||
716 | [Parent] | ||
717 | Name = Unknwon | ||
718 | Age = 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 | |||
730 | By 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 | |||
734 | Many 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 | |||
736 | To 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 | |||
740 | This 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 | ||
43 | cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data")))) | ||
44 | ``` | ||
45 | |||
46 | 或者从一个空白的文件开始: | ||
47 | |||
48 | ```go | ||
49 | cfg := ini.Empty() | ||
50 | ``` | ||
51 | |||
52 | 当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。 | ||
53 | |||
54 | ```go | ||
55 | err := cfg.Append("other file", []byte("other raw data")) | ||
56 | ``` | ||
57 | |||
58 | 当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误): | ||
59 | |||
60 | ```go | ||
61 | cfg, err := ini.LooseLoad("filename", "filename_404") | ||
62 | ``` | ||
63 | |||
64 | 更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。 | ||
65 | |||
66 | #### 忽略键名的大小写 | ||
67 | |||
68 | 有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写: | ||
69 | |||
70 | ```go | ||
71 | cfg, err := ini.InsensitiveLoad("filename") | ||
72 | //... | ||
73 | |||
74 | // sec1 和 sec2 指向同一个分区对象 | ||
75 | sec1, err := cfg.GetSection("Section") | ||
76 | sec2, err := cfg.GetSection("SecTIOn") | ||
77 | |||
78 | // key1 和 key2 指向同一个键对象 | ||
79 | key1, err := cfg.GetKey("Key") | ||
80 | key2, err := cfg.GetKey("KeY") | ||
81 | ``` | ||
82 | |||
83 | #### 类似 MySQL 配置中的布尔值键 | ||
84 | |||
85 | MySQL 的配置文件中会出现没有具体值的布尔类型的键: | ||
86 | |||
87 | ```ini | ||
88 | [mysqld] | ||
89 | ... | ||
90 | skip-host-cache | ||
91 | skip-name-resolve | ||
92 | ``` | ||
93 | |||
94 | 默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理: | ||
95 | |||
96 | ```go | ||
97 | cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf")) | ||
98 | ``` | ||
99 | |||
100 | 这些键的值永远为 `true`,且在保存到文件时也只会输出键名。 | ||
101 | |||
102 | 如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`: | ||
103 | |||
104 | ```go | ||
105 | key, err := sec.NewBooleanKey("skip-host-cache") | ||
106 | ``` | ||
107 | |||
108 | #### 关于注释 | ||
109 | |||
110 | 下述几种情况的内容将被视为注释: | ||
111 | |||
112 | 1. 所有以 `#` 或 `;` 开头的行 | ||
113 | 2. 所有在 `#` 或 `;` 之后的内容 | ||
114 | 3. 分区标签后的文字 (即 `[分区名]` 之后的内容) | ||
115 | |||
116 | 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。 | ||
117 | |||
118 | ### 操作分区(Section) | ||
119 | |||
120 | 获取指定分区: | ||
121 | |||
122 | ```go | ||
123 | section, err := cfg.GetSection("section name") | ||
124 | ``` | ||
125 | |||
126 | 如果您想要获取默认分区,则可以用空字符串代替分区名: | ||
127 | |||
128 | ```go | ||
129 | section, err := cfg.GetSection("") | ||
130 | ``` | ||
131 | |||
132 | 当您非常确定某个分区是存在的,可以使用以下简便方法: | ||
133 | |||
134 | ```go | ||
135 | section := cfg.Section("section name") | ||
136 | ``` | ||
137 | |||
138 | 如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。 | ||
139 | |||
140 | 创建一个分区: | ||
141 | |||
142 | ```go | ||
143 | err := cfg.NewSection("new section") | ||
144 | ``` | ||
145 | |||
146 | 获取所有分区对象或名称: | ||
147 | |||
148 | ```go | ||
149 | sections := cfg.Sections() | ||
150 | names := cfg.SectionStrings() | ||
151 | ``` | ||
152 | |||
153 | ### 操作键(Key) | ||
154 | |||
155 | 获取某个分区下的键: | ||
156 | |||
157 | ```go | ||
158 | key, err := cfg.Section("").GetKey("key name") | ||
159 | ``` | ||
160 | |||
161 | 和分区一样,您也可以直接获取键而忽略错误处理: | ||
162 | |||
163 | ```go | ||
164 | key := cfg.Section("").Key("key name") | ||
165 | ``` | ||
166 | |||
167 | 判断某个键是否存在: | ||
168 | |||
169 | ```go | ||
170 | yes := cfg.Section("").HasKey("key name") | ||
171 | ``` | ||
172 | |||
173 | 创建一个新的键: | ||
174 | |||
175 | ```go | ||
176 | err := cfg.Section("").NewKey("name", "value") | ||
177 | ``` | ||
178 | |||
179 | 获取分区下的所有键或键名: | ||
180 | |||
181 | ```go | ||
182 | keys := cfg.Section("").Keys() | ||
183 | names := cfg.Section("").KeyStrings() | ||
184 | ``` | ||
185 | |||
186 | 获取分区下的所有键值对的克隆: | ||
187 | |||
188 | ```go | ||
189 | hash := cfg.Section("").KeysHash() | ||
190 | ``` | ||
191 | |||
192 | ### 操作键值(Value) | ||
193 | |||
194 | 获取一个类型为字符串(string)的值: | ||
195 | |||
196 | ```go | ||
197 | val := cfg.Section("").Key("key name").String() | ||
198 | ``` | ||
199 | |||
200 | 获取值的同时通过自定义函数进行处理验证: | ||
201 | |||
202 | ```go | ||
203 | val := 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 | ||
214 | val := cfg.Section("").Key("key name").Value() | ||
215 | ``` | ||
216 | |||
217 | 判断某个原值是否存在: | ||
218 | |||
219 | ```go | ||
220 | yes := 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 | ||
229 | v, err = cfg.Section("").Key("BOOL").Bool() | ||
230 | v, err = cfg.Section("").Key("FLOAT64").Float64() | ||
231 | v, err = cfg.Section("").Key("INT").Int() | ||
232 | v, err = cfg.Section("").Key("INT64").Int64() | ||
233 | v, err = cfg.Section("").Key("UINT").Uint() | ||
234 | v, err = cfg.Section("").Key("UINT64").Uint64() | ||
235 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339) | ||
236 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339 | ||
237 | |||
238 | v = cfg.Section("").Key("BOOL").MustBool() | ||
239 | v = cfg.Section("").Key("FLOAT64").MustFloat64() | ||
240 | v = cfg.Section("").Key("INT").MustInt() | ||
241 | v = cfg.Section("").Key("INT64").MustInt64() | ||
242 | v = cfg.Section("").Key("UINT").MustUint() | ||
243 | v = cfg.Section("").Key("UINT64").MustUint64() | ||
244 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339) | ||
245 | v = cfg.Section("").Key("TIME").MustTime() // RFC3339 | ||
246 | |||
247 | // 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值, | ||
248 | // 当键不存在或者转换失败时,则会直接返回该默认值。 | ||
249 | // 但是,MustString 方法必须传递一个默认值。 | ||
250 | |||
251 | v = cfg.Seciont("").Key("String").MustString("default") | ||
252 | v = cfg.Section("").Key("BOOL").MustBool(true) | ||
253 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25) | ||
254 | v = cfg.Section("").Key("INT").MustInt(10) | ||
255 | v = cfg.Section("").Key("INT64").MustInt64(99) | ||
256 | v = cfg.Section("").Key("UINT").MustUint(3) | ||
257 | v = cfg.Section("").Key("UINT64").MustUint64(6) | ||
258 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now()) | ||
259 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339 | ||
260 | ``` | ||
261 | |||
262 | 如果我的值有好多行怎么办? | ||
263 | |||
264 | ```ini | ||
265 | [advance] | ||
266 | ADDRESS = """404 road, | ||
267 | NotFound, State, 5000 | ||
268 | Earth""" | ||
269 | ``` | ||
270 | |||
271 | 嗯哼?小 case! | ||
272 | |||
273 | ```go | ||
274 | cfg.Section("advance").Key("ADDRESS").String() | ||
275 | |||
276 | /* --- start --- | ||
277 | 404 road, | ||
278 | NotFound, State, 5000 | ||
279 | Earth | ||
280 | ------ end --- */ | ||
281 | ``` | ||
282 | |||
283 | 赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办? | ||
284 | |||
285 | ```ini | ||
286 | [advance] | ||
287 | two_lines = how about \ | ||
288 | continuation lines? | ||
289 | lots_of_lines = 1 \ | ||
290 | 2 \ | ||
291 | 3 \ | ||
292 | 4 | ||
293 | ``` | ||
294 | |||
295 | 简直是小菜一碟! | ||
296 | |||
297 | ```go | ||
298 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines? | ||
299 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4 | ||
300 | ``` | ||
301 | |||
302 | 可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢? | ||
303 | |||
304 | ```go | ||
305 | cfg, err := ini.LoadSources(ini.LoadOptions{ | ||
306 | IgnoreContinuation: true, | ||
307 | }, "filename") | ||
308 | ``` | ||
309 | |||
310 | 哇靠给力啊! | ||
311 | |||
312 | 需要注意的是,值两侧的单引号会被自动剔除: | ||
313 | |||
314 | ```ini | ||
315 | foo = "some value" // foo: some value | ||
316 | bar = 'some value' // bar: some value | ||
317 | ``` | ||
318 | |||
319 | 这就是全部了?哈哈,当然不是。 | ||
320 | |||
321 | #### 操作键值的辅助方法 | ||
322 | |||
323 | 获取键值时设定候选值: | ||
324 | |||
325 | ```go | ||
326 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"}) | ||
327 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75}) | ||
328 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30}) | ||
329 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30}) | ||
330 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9}) | ||
331 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9}) | ||
332 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3}) | ||
333 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339 | ||
334 | ``` | ||
335 | |||
336 | 如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。 | ||
337 | |||
338 | 验证获取的值是否在指定范围内: | ||
339 | |||
340 | ```go | ||
341 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2) | ||
342 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20) | ||
343 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20) | ||
344 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9) | ||
345 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9) | ||
346 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime) | ||
347 | vals = 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] | ||
357 | vals = cfg.Section("").Key("STRINGS").Strings(",") | ||
358 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",") | ||
359 | vals = cfg.Section("").Key("INTS").Ints(",") | ||
360 | vals = cfg.Section("").Key("INT64S").Int64s(",") | ||
361 | vals = cfg.Section("").Key("UINTS").Uints(",") | ||
362 | vals = cfg.Section("").Key("UINT64S").Uint64s(",") | ||
363 | vals = 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] | ||
371 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",") | ||
372 | vals = cfg.Section("").Key("INTS").ValidInts(",") | ||
373 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",") | ||
374 | vals = cfg.Section("").Key("UINTS").ValidUints(",") | ||
375 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",") | ||
376 | vals = 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 | ||
384 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",") | ||
385 | vals = cfg.Section("").Key("INTS").StrictInts(",") | ||
386 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",") | ||
387 | vals = cfg.Section("").Key("UINTS").StrictUints(",") | ||
388 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",") | ||
389 | vals = cfg.Section("").Key("TIMES").StrictTimes(",") | ||
390 | ``` | ||
391 | |||
392 | ### 保存配置 | ||
393 | |||
394 | 终于到了这个时刻,是时候保存一下配置了。 | ||
395 | |||
396 | 比较原始的做法是输出配置到某个文件: | ||
397 | |||
398 | ```go | ||
399 | // ... | ||
400 | err = cfg.SaveTo("my.ini") | ||
401 | err = cfg.SaveToIndent("my.ini", "\t") | ||
402 | ``` | ||
403 | |||
404 | 另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中: | ||
405 | |||
406 | ```go | ||
407 | // ... | ||
408 | cfg.WriteTo(writer) | ||
409 | cfg.WriteToIndent(writer, "\t") | ||
410 | ``` | ||
411 | |||
412 | 默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能: | ||
413 | |||
414 | ```go | ||
415 | ini.PrettyFormat = false | ||
416 | ``` | ||
417 | |||
418 | ## 高级用法 | ||
419 | |||
420 | ### 递归读取键值 | ||
421 | |||
422 | 在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。 | ||
423 | |||
424 | ```ini | ||
425 | NAME = ini | ||
426 | |||
427 | [author] | ||
428 | NAME = Unknwon | ||
429 | GITHUB = https://github.com/%(NAME)s | ||
430 | |||
431 | [package] | ||
432 | FULL_NAME = github.com/go-ini/%(NAME)s | ||
433 | ``` | ||
434 | |||
435 | ```go | ||
436 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon | ||
437 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini | ||
438 | ``` | ||
439 | |||
440 | ### 读取父子分区 | ||
441 | |||
442 | 您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。 | ||
443 | |||
444 | ```ini | ||
445 | NAME = ini | ||
446 | VERSION = v1 | ||
447 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s | ||
448 | |||
449 | [package] | ||
450 | CLONE_URL = https://%(IMPORT_PATH)s | ||
451 | |||
452 | [package.sub] | ||
453 | ``` | ||
454 | |||
455 | ```go | ||
456 | cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1 | ||
457 | ``` | ||
458 | |||
459 | #### 获取上级父分区下的所有键名 | ||
460 | |||
461 | ```go | ||
462 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] | ||
463 | ``` | ||
464 | |||
465 | ### 无法解析的分区 | ||
466 | |||
467 | 如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理: | ||
468 | |||
469 | ```go | ||
470 | cfg, 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 | |||
473 | body := 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 | ||
492 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"} | ||
493 | ``` | ||
494 | |||
495 | ### 映射到结构 | ||
496 | |||
497 | 想要使用更加面向对象的方式玩转 INI 吗?好主意。 | ||
498 | |||
499 | ```ini | ||
500 | Name = Unknwon | ||
501 | age = 21 | ||
502 | Male = true | ||
503 | Born = 1993-01-01T20:17:05Z | ||
504 | |||
505 | [Note] | ||
506 | Content = Hi is a good man! | ||
507 | Cities = HangZhou, Boston | ||
508 | ``` | ||
509 | |||
510 | ```go | ||
511 | type Note struct { | ||
512 | Content string | ||
513 | Cities []string | ||
514 | } | ||
515 | |||
516 | type 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 | |||
525 | func 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 | // ... | ||
547 | p := &Person{ | ||
548 | Name: "Joe", | ||
549 | } | ||
550 | // ... | ||
551 | ``` | ||
552 | |||
553 | 这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用? | ||
554 | |||
555 | ### 从结构反射 | ||
556 | |||
557 | 可是,我有说不能吗? | ||
558 | |||
559 | ```go | ||
560 | type Embeded struct { | ||
561 | Dates []time.Time `delim:"|"` | ||
562 | Places []string `ini:"places,omitempty"` | ||
563 | None []int `ini:",omitempty"` | ||
564 | } | ||
565 | |||
566 | type Author struct { | ||
567 | Name string `ini:"NAME"` | ||
568 | Male bool | ||
569 | Age int | ||
570 | GPA float64 | ||
571 | NeverMind string `ini:"-"` | ||
572 | *Embeded | ||
573 | } | ||
574 | |||
575 | func 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 | ||
591 | NAME = Unknwon | ||
592 | Male = true | ||
593 | Age = 21 | ||
594 | GPA = 2.8 | ||
595 | |||
596 | [Embeded] | ||
597 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00 | ||
598 | places = 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 | ||
613 | type Info struct{ | ||
614 | PackageName string | ||
615 | } | ||
616 | |||
617 | func 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 | ||
637 | type Env struct { | ||
638 | Foo string `ini:"foo"` | ||
639 | } | ||
640 | |||
641 | func 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 | ||
657 | type Child struct { | ||
658 | Age string | ||
659 | } | ||
660 | |||
661 | type Parent struct { | ||
662 | Name string | ||
663 | Child | ||
664 | } | ||
665 | |||
666 | type Config struct { | ||
667 | City string | ||
668 | Parent | ||
669 | } | ||
670 | ``` | ||
671 | |||
672 | 示例配置文件: | ||
673 | |||
674 | ```ini | ||
675 | City = Boston | ||
676 | |||
677 | [Parent] | ||
678 | Name = Unknwon | ||
679 | |||
680 | [Child] | ||
681 | Age = 21 | ||
682 | ``` | ||
683 | |||
684 | 很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚! | ||
685 | |||
686 | ```go | ||
687 | type Child struct { | ||
688 | Age string | ||
689 | } | ||
690 | |||
691 | type Parent struct { | ||
692 | Name string | ||
693 | Child `ini:"Parent"` | ||
694 | } | ||
695 | |||
696 | type Config struct { | ||
697 | City string | ||
698 | Parent | ||
699 | } | ||
700 | ``` | ||
701 | |||
702 | 示例配置文件: | ||
703 | |||
704 | ```ini | ||
705 | City = Boston | ||
706 | |||
707 | [Parent] | ||
708 | Name = Unknwon | ||
709 | Age = 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 | |||
15 | package ini | ||
16 | |||
17 | import ( | ||
18 | "fmt" | ||
19 | ) | ||
20 | |||
21 | type ErrDelimiterNotFound struct { | ||
22 | Line string | ||
23 | } | ||
24 | |||
25 | func IsErrDelimiterNotFound(err error) bool { | ||
26 | _, ok := err.(ErrDelimiterNotFound) | ||
27 | return ok | ||
28 | } | ||
29 | |||
30 | func (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. | ||
16 | package ini | ||
17 | |||
18 | import ( | ||
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 | |||
33 | const ( | ||
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. | ||
44 | func Version() string { | ||
45 | return _VERSION | ||
46 | } | ||
47 | |||
48 | var ( | ||
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 | |||
65 | func init() { | ||
66 | if runtime.GOOS == "windows" { | ||
67 | LineBreak = "\r\n" | ||
68 | } | ||
69 | } | ||
70 | |||
71 | func 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. | ||
81 | type dataSource interface { | ||
82 | ReadCloser() (io.ReadCloser, error) | ||
83 | } | ||
84 | |||
85 | // sourceFile represents an object that contains content on the local file system. | ||
86 | type sourceFile struct { | ||
87 | name string | ||
88 | } | ||
89 | |||
90 | func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { | ||
91 | return os.Open(s.name) | ||
92 | } | ||
93 | |||
94 | type bytesReadCloser struct { | ||
95 | reader io.Reader | ||
96 | } | ||
97 | |||
98 | func (rc *bytesReadCloser) Read(p []byte) (n int, err error) { | ||
99 | return rc.reader.Read(p) | ||
100 | } | ||
101 | |||
102 | func (rc *bytesReadCloser) Close() error { | ||
103 | return nil | ||
104 | } | ||
105 | |||
106 | // sourceData represents an object that contains content in memory. | ||
107 | type sourceData struct { | ||
108 | data []byte | ||
109 | } | ||
110 | |||
111 | func (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. | ||
116 | type sourceReadCloser struct { | ||
117 | reader io.ReadCloser | ||
118 | } | ||
119 | |||
120 | func (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. | ||
125 | type 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. | ||
146 | func 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 | |||
156 | func 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 | |||
169 | type 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 | |||
186 | func 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. | ||
208 | func 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. | ||
214 | func 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. | ||
220 | func 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. | ||
226 | func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { | ||
227 | return LoadSources(LoadOptions{AllowShadows: true}, source, others...) | ||
228 | } | ||
229 | |||
230 | // Empty returns an empty file object. | ||
231 | func 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. | ||
238 | func (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. | ||
260 | func (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. | ||
272 | func (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. | ||
282 | func (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. | ||
302 | func (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. | ||
314 | func (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. | ||
323 | func (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. | ||
330 | func (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 | |||
349 | func (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. | ||
360 | func (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. | ||
375 | func (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. | ||
394 | func (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. | ||
519 | func (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. | ||
524 | func (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. | ||
547 | func (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 | |||
15 | package ini | ||
16 | |||
17 | import ( | ||
18 | "errors" | ||
19 | "fmt" | ||
20 | "strconv" | ||
21 | "strings" | ||
22 | "time" | ||
23 | ) | ||
24 | |||
25 | // Key represents a key under a section. | ||
26 | type 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. | ||
40 | func newKey(s *Section, name, val string) *Key { | ||
41 | return &Key{ | ||
42 | s: s, | ||
43 | name: name, | ||
44 | value: val, | ||
45 | } | ||
46 | } | ||
47 | |||
48 | func (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. | ||
62 | func (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 | ||
70 | type ValueMapper func(string) string | ||
71 | |||
72 | // Name returns name of key. | ||
73 | func (k *Key) Name() string { | ||
74 | return k.name | ||
75 | } | ||
76 | |||
77 | // Value returns raw value of key for performance purpose. | ||
78 | func (k *Key) Value() string { | ||
79 | return k.value | ||
80 | } | ||
81 | |||
82 | // ValueWithShadows returns raw values of key and its shadows if any. | ||
83 | func (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. | ||
96 | func (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. | ||
129 | func (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. | ||
135 | func (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. | ||
144 | func 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. | ||
155 | func (k *Key) Bool() (bool, error) { | ||
156 | return parseBool(k.String()) | ||
157 | } | ||
158 | |||
159 | // Float64 returns float64 type value. | ||
160 | func (k *Key) Float64() (float64, error) { | ||
161 | return strconv.ParseFloat(k.String(), 64) | ||
162 | } | ||
163 | |||
164 | // Int returns int type value. | ||
165 | func (k *Key) Int() (int, error) { | ||
166 | return strconv.Atoi(k.String()) | ||
167 | } | ||
168 | |||
169 | // Int64 returns int64 type value. | ||
170 | func (k *Key) Int64() (int64, error) { | ||
171 | return strconv.ParseInt(k.String(), 10, 64) | ||
172 | } | ||
173 | |||
174 | // Uint returns uint type valued. | ||
175 | func (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. | ||
181 | func (k *Key) Uint64() (uint64, error) { | ||
182 | return strconv.ParseUint(k.String(), 10, 64) | ||
183 | } | ||
184 | |||
185 | // Duration returns time.Duration type value. | ||
186 | func (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. | ||
191 | func (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. | ||
196 | func (k *Key) Time() (time.Time, error) { | ||
197 | return k.TimeFormat(time.RFC3339) | ||
198 | } | ||
199 | |||
200 | // MustString returns default value if key value is empty. | ||
201 | func (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. | ||
212 | func (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. | ||
223 | func (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. | ||
234 | func (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. | ||
245 | func (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. | ||
256 | func (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. | ||
267 | func (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. | ||
278 | func (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. | ||
289 | func (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. | ||
300 | func (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. | ||
306 | func (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. | ||
318 | func (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. | ||
330 | func (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. | ||
342 | func (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. | ||
354 | func (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. | ||
366 | func (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. | ||
378 | func (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. | ||
390 | func (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. | ||
396 | func (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. | ||
406 | func (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. | ||
416 | func (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. | ||
426 | func (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. | ||
436 | func (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. | ||
441 | func (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. | ||
457 | func (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. | ||
475 | func (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. | ||
481 | func (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. | ||
487 | func (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. | ||
493 | func (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. | ||
499 | func (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). | ||
506 | func (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). | ||
513 | func (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. | ||
519 | func (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. | ||
526 | func (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. | ||
533 | func (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. | ||
540 | func (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. | ||
547 | func (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. | ||
553 | func (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. | ||
559 | func (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. | ||
564 | func (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. | ||
569 | func (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. | ||
574 | func (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. | ||
579 | func (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. | ||
584 | func (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. | ||
590 | func (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. | ||
596 | func (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. | ||
601 | func (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. | ||
617 | func (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. | ||
632 | func (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. | ||
647 | func (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. | ||
663 | func (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. | ||
679 | func (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. | ||
695 | func (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 | |||
15 | package ini | ||
16 | |||
17 | import ( | ||
18 | "bufio" | ||
19 | "bytes" | ||
20 | "fmt" | ||
21 | "io" | ||
22 | "strconv" | ||
23 | "strings" | ||
24 | "unicode" | ||
25 | ) | ||
26 | |||
27 | type tokenType int | ||
28 | |||
29 | const ( | ||
30 | _TOKEN_INVALID tokenType = iota | ||
31 | _TOKEN_COMMENT | ||
32 | _TOKEN_SECTION | ||
33 | _TOKEN_KEY | ||
34 | ) | ||
35 | |||
36 | type parser struct { | ||
37 | buf *bufio.Reader | ||
38 | isEOF bool | ||
39 | count int | ||
40 | comment *bytes.Buffer | ||
41 | } | ||
42 | |||
43 | func newParser(r io.Reader) *parser { | ||
44 | return &parser{ | ||
45 | buf: bufio.NewReader(r), | ||
46 | count: 1, | ||
47 | comment: &bytes.Buffer{}, | ||
48 | } | ||
49 | } | ||
50 | |||
51 | // BOM handles header of 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 | ||
53 | func (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 | |||
80 | func (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 | |||
92 | func 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 | |||
100 | func 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 | |||
142 | func (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 | |||
168 | func (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. | ||
191 | func 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 | |||
196 | func (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. | ||
243 | func (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 | |||
15 | package ini | ||
16 | |||
17 | import ( | ||
18 | "errors" | ||
19 | "fmt" | ||
20 | "strings" | ||
21 | ) | ||
22 | |||
23 | // Section represents a config section. | ||
24 | type 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 | |||
36 | func 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. | ||
47 | func (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. | ||
53 | func (s *Section) Body() string { | ||
54 | return strings.TrimSpace(s.rawBody) | ||
55 | } | ||
56 | |||
57 | // NewKey creates a new key to given section. | ||
58 | func (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. | ||
88 | func (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. | ||
99 | func (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. | ||
133 | func (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. | ||
139 | func (s *Section) Haskey(name string) bool { | ||
140 | return s.HasKey(name) | ||
141 | } | ||
142 | |||
143 | // HasValue returns true if section contains given raw value. | ||
144 | func (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. | ||
159 | func (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. | ||
171 | func (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. | ||
180 | func (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. | ||
200 | func (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. | ||
207 | func (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. | ||
221 | func (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 | |||
15 | package ini | ||
16 | |||
17 | import ( | ||
18 | "bytes" | ||
19 | "errors" | ||
20 | "fmt" | ||
21 | "reflect" | ||
22 | "strings" | ||
23 | "time" | ||
24 | "unicode" | ||
25 | ) | ||
26 | |||
27 | // NameMapper represents a ini tag name mapper. | ||
28 | type NameMapper func(string) string | ||
29 | |||
30 | // Built-in name getters. | ||
31 | var ( | ||
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 | |||
61 | func (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 | |||
71 | func parseDelim(actual string) string { | ||
72 | if len(actual) > 0 { | ||
73 | return actual | ||
74 | } | ||
75 | return "," | ||
76 | } | ||
77 | |||
78 | var reflectTime = reflect.TypeOf(time.Now()).Kind() | ||
79 | |||
80 | // setSliceWithProperType sets proper values to slice based on its type. | ||
81 | func 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. | ||
142 | func 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 | |||
203 | func 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 | |||
215 | func (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. | ||
262 | func (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. | ||
276 | func (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. | ||
281 | func 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. | ||
291 | func MapTo(v, source interface{}, others ...interface{}) error { | ||
292 | return MapToWithMapper(v, nil, source, others...) | ||
293 | } | ||
294 | |||
295 | // reflectSliceWithProperType does the opposite thing as setSliceWithProperType. | ||
296 | func 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. | ||
326 | func 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. | ||
350 | func 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 | |||
370 | func (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. | ||
423 | func (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. | ||
437 | func (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. | ||
442 | func 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. | ||
448 | func ReflectFrom(cfg *File, v interface{}) error { | ||
449 | return ReflectFromWithMapper(cfg, v, nil) | ||
450 | } | ||