diff options
Diffstat (limited to 'vendor/github.com/go-ini/ini/README.md')
-rw-r--r-- | vendor/github.com/go-ini/ini/README.md | 734 |
1 files changed, 734 insertions, 0 deletions
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. | ||