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