]>
Commit | Line | Data |
---|---|---|
15c0b25d | 1 | INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge) |
bae9f6d2 JC |
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 | ||
15c0b25d AP |
109 | To generate such keys in your program, you could use `NewBooleanKey`: |
110 | ||
111 | ```go | |
112 | key, err := sec.NewBooleanKey("skip-host-cache") | |
113 | ``` | |
114 | ||
bae9f6d2 JC |
115 | #### Comment |
116 | ||
117 | Take care that following format will be treated as comment: | |
118 | ||
119 | 1. Line begins with `#` or `;` | |
120 | 2. Words after `#` or `;` | |
121 | 3. Words after section name (i.e words after `[some section name]`) | |
122 | ||
123 | If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```. | |
124 | ||
125 | ### Working with sections | |
126 | ||
127 | To get a section, you would need to: | |
128 | ||
129 | ```go | |
130 | section, err := cfg.GetSection("section name") | |
131 | ``` | |
132 | ||
133 | For a shortcut for default section, just give an empty string as name: | |
134 | ||
135 | ```go | |
136 | section, err := cfg.GetSection("") | |
137 | ``` | |
138 | ||
139 | When you're pretty sure the section exists, following code could make your life easier: | |
140 | ||
141 | ```go | |
142 | section := cfg.Section("section name") | |
143 | ``` | |
144 | ||
145 | What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you. | |
146 | ||
147 | To create a new section: | |
148 | ||
149 | ```go | |
150 | err := cfg.NewSection("new section") | |
151 | ``` | |
152 | ||
153 | To get a list of sections or section names: | |
154 | ||
155 | ```go | |
156 | sections := cfg.Sections() | |
157 | names := cfg.SectionStrings() | |
158 | ``` | |
159 | ||
160 | ### Working with keys | |
161 | ||
162 | To get a key under a section: | |
163 | ||
164 | ```go | |
165 | key, err := cfg.Section("").GetKey("key name") | |
166 | ``` | |
167 | ||
168 | Same rule applies to key operations: | |
169 | ||
170 | ```go | |
171 | key := cfg.Section("").Key("key name") | |
172 | ``` | |
173 | ||
174 | To check if a key exists: | |
175 | ||
176 | ```go | |
177 | yes := cfg.Section("").HasKey("key name") | |
178 | ``` | |
179 | ||
180 | To create a new key: | |
181 | ||
182 | ```go | |
183 | err := cfg.Section("").NewKey("name", "value") | |
184 | ``` | |
185 | ||
186 | To get a list of keys or key names: | |
187 | ||
188 | ```go | |
189 | keys := cfg.Section("").Keys() | |
190 | names := cfg.Section("").KeyStrings() | |
191 | ``` | |
192 | ||
193 | To get a clone hash of keys and corresponding values: | |
194 | ||
195 | ```go | |
196 | hash := cfg.Section("").KeysHash() | |
197 | ``` | |
198 | ||
199 | ### Working with values | |
200 | ||
201 | To get a string value: | |
202 | ||
203 | ```go | |
204 | val := cfg.Section("").Key("key name").String() | |
205 | ``` | |
206 | ||
207 | To validate key value on the fly: | |
208 | ||
209 | ```go | |
210 | val := cfg.Section("").Key("key name").Validate(func(in string) string { | |
211 | if len(in) == 0 { | |
212 | return "default" | |
213 | } | |
214 | return in | |
215 | }) | |
216 | ``` | |
217 | ||
218 | If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance): | |
219 | ||
220 | ```go | |
221 | val := cfg.Section("").Key("key name").Value() | |
222 | ``` | |
223 | ||
224 | To check if raw value exists: | |
225 | ||
226 | ```go | |
227 | yes := cfg.Section("").HasValue("test value") | |
228 | ``` | |
229 | ||
230 | To get value with types: | |
231 | ||
232 | ```go | |
233 | // For boolean values: | |
234 | // true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On | |
235 | // false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off | |
236 | v, err = cfg.Section("").Key("BOOL").Bool() | |
237 | v, err = cfg.Section("").Key("FLOAT64").Float64() | |
238 | v, err = cfg.Section("").Key("INT").Int() | |
239 | v, err = cfg.Section("").Key("INT64").Int64() | |
240 | v, err = cfg.Section("").Key("UINT").Uint() | |
241 | v, err = cfg.Section("").Key("UINT64").Uint64() | |
242 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339) | |
243 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339 | |
244 | ||
245 | v = cfg.Section("").Key("BOOL").MustBool() | |
246 | v = cfg.Section("").Key("FLOAT64").MustFloat64() | |
247 | v = cfg.Section("").Key("INT").MustInt() | |
248 | v = cfg.Section("").Key("INT64").MustInt64() | |
249 | v = cfg.Section("").Key("UINT").MustUint() | |
250 | v = cfg.Section("").Key("UINT64").MustUint64() | |
251 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339) | |
252 | v = cfg.Section("").Key("TIME").MustTime() // RFC3339 | |
253 | ||
254 | // Methods start with Must also accept one argument for default value | |
255 | // when key not found or fail to parse value to given type. | |
256 | // Except method MustString, which you have to pass a default value. | |
257 | ||
258 | v = cfg.Section("").Key("String").MustString("default") | |
259 | v = cfg.Section("").Key("BOOL").MustBool(true) | |
260 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25) | |
261 | v = cfg.Section("").Key("INT").MustInt(10) | |
262 | v = cfg.Section("").Key("INT64").MustInt64(99) | |
263 | v = cfg.Section("").Key("UINT").MustUint(3) | |
264 | v = cfg.Section("").Key("UINT64").MustUint64(6) | |
265 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now()) | |
266 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339 | |
267 | ``` | |
268 | ||
269 | What if my value is three-line long? | |
270 | ||
271 | ```ini | |
272 | [advance] | |
273 | ADDRESS = """404 road, | |
274 | NotFound, State, 5000 | |
275 | Earth""" | |
276 | ``` | |
277 | ||
278 | Not a problem! | |
279 | ||
280 | ```go | |
281 | cfg.Section("advance").Key("ADDRESS").String() | |
282 | ||
283 | /* --- start --- | |
284 | 404 road, | |
285 | NotFound, State, 5000 | |
286 | Earth | |
287 | ------ end --- */ | |
288 | ``` | |
289 | ||
290 | That's cool, how about continuation lines? | |
291 | ||
292 | ```ini | |
293 | [advance] | |
294 | two_lines = how about \ | |
295 | continuation lines? | |
296 | lots_of_lines = 1 \ | |
297 | 2 \ | |
298 | 3 \ | |
299 | 4 | |
300 | ``` | |
301 | ||
302 | Piece of cake! | |
303 | ||
304 | ```go | |
305 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines? | |
306 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4 | |
307 | ``` | |
308 | ||
309 | Well, I hate continuation lines, how do I disable that? | |
310 | ||
311 | ```go | |
312 | cfg, err := ini.LoadSources(ini.LoadOptions{ | |
313 | IgnoreContinuation: true, | |
314 | }, "filename") | |
315 | ``` | |
316 | ||
317 | Holy crap! | |
318 | ||
319 | Note that single quotes around values will be stripped: | |
320 | ||
321 | ```ini | |
322 | foo = "some value" // foo: some value | |
323 | bar = 'some value' // bar: some value | |
324 | ``` | |
325 | ||
326 | That's all? Hmm, no. | |
327 | ||
328 | #### Helper methods of working with values | |
329 | ||
330 | To get value with given candidates: | |
331 | ||
332 | ```go | |
333 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"}) | |
334 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75}) | |
335 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30}) | |
336 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30}) | |
337 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9}) | |
338 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9}) | |
339 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3}) | |
340 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339 | |
341 | ``` | |
342 | ||
343 | Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates. | |
344 | ||
345 | To validate value in a given range: | |
346 | ||
347 | ```go | |
348 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2) | |
349 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20) | |
350 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20) | |
351 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9) | |
352 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9) | |
353 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime) | |
354 | vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339 | |
355 | ``` | |
356 | ||
357 | ##### Auto-split values into a slice | |
358 | ||
359 | To use zero value of type for invalid inputs: | |
360 | ||
361 | ```go | |
362 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | |
363 | // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0] | |
364 | vals = cfg.Section("").Key("STRINGS").Strings(",") | |
365 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",") | |
366 | vals = cfg.Section("").Key("INTS").Ints(",") | |
367 | vals = cfg.Section("").Key("INT64S").Int64s(",") | |
368 | vals = cfg.Section("").Key("UINTS").Uints(",") | |
369 | vals = cfg.Section("").Key("UINT64S").Uint64s(",") | |
370 | vals = cfg.Section("").Key("TIMES").Times(",") | |
371 | ``` | |
372 | ||
373 | To exclude invalid values out of result slice: | |
374 | ||
375 | ```go | |
376 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | |
377 | // Input: how, 2.2, are, you -> [2.2] | |
378 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",") | |
379 | vals = cfg.Section("").Key("INTS").ValidInts(",") | |
380 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",") | |
381 | vals = cfg.Section("").Key("UINTS").ValidUints(",") | |
382 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",") | |
383 | vals = cfg.Section("").Key("TIMES").ValidTimes(",") | |
384 | ``` | |
385 | ||
386 | Or to return nothing but error when have invalid inputs: | |
387 | ||
388 | ```go | |
389 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4] | |
390 | // Input: how, 2.2, are, you -> error | |
391 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",") | |
392 | vals = cfg.Section("").Key("INTS").StrictInts(",") | |
393 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",") | |
394 | vals = cfg.Section("").Key("UINTS").StrictUints(",") | |
395 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",") | |
396 | vals = cfg.Section("").Key("TIMES").StrictTimes(",") | |
397 | ``` | |
398 | ||
399 | ### Save your configuration | |
400 | ||
401 | Finally, it's time to save your configuration to somewhere. | |
402 | ||
403 | A typical way to save configuration is writing it to a file: | |
404 | ||
405 | ```go | |
406 | // ... | |
407 | err = cfg.SaveTo("my.ini") | |
408 | err = cfg.SaveToIndent("my.ini", "\t") | |
409 | ``` | |
410 | ||
411 | Another way to save is writing to a `io.Writer` interface: | |
412 | ||
413 | ```go | |
414 | // ... | |
415 | cfg.WriteTo(writer) | |
416 | cfg.WriteToIndent(writer, "\t") | |
417 | ``` | |
418 | ||
419 | By default, spaces are used to align "=" sign between key and values, to disable that: | |
420 | ||
421 | ```go | |
422 | ini.PrettyFormat = false | |
423 | ``` | |
424 | ||
425 | ## Advanced Usage | |
426 | ||
427 | ### Recursive Values | |
428 | ||
429 | For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions. | |
430 | ||
431 | ```ini | |
432 | NAME = ini | |
433 | ||
434 | [author] | |
435 | NAME = Unknwon | |
436 | GITHUB = https://github.com/%(NAME)s | |
437 | ||
438 | [package] | |
439 | FULL_NAME = github.com/go-ini/%(NAME)s | |
440 | ``` | |
441 | ||
442 | ```go | |
443 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon | |
444 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini | |
445 | ``` | |
446 | ||
447 | ### Parent-child Sections | |
448 | ||
449 | You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section. | |
450 | ||
451 | ```ini | |
452 | NAME = ini | |
453 | VERSION = v1 | |
454 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s | |
455 | ||
456 | [package] | |
457 | CLONE_URL = https://%(IMPORT_PATH)s | |
458 | ||
459 | [package.sub] | |
460 | ``` | |
461 | ||
462 | ```go | |
463 | cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1 | |
464 | ``` | |
465 | ||
466 | #### Retrieve parent keys available to a child section | |
467 | ||
468 | ```go | |
469 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"] | |
470 | ``` | |
471 | ||
472 | ### Unparseable Sections | |
473 | ||
474 | Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`: | |
475 | ||
476 | ```go | |
477 | cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS] | |
478 | <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)) | |
479 | ||
480 | body := cfg.Section("COMMENTS").Body() | |
481 | ||
482 | /* --- start --- | |
483 | <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1> | |
484 | ------ end --- */ | |
485 | ``` | |
486 | ||
487 | ### Auto-increment Key Names | |
488 | ||
489 | If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter. | |
490 | ||
491 | ```ini | |
492 | [features] | |
493 | -: Support read/write comments of keys and sections | |
494 | -: Support auto-increment of key names | |
495 | -: Support load multiple files to overwrite key values | |
496 | ``` | |
497 | ||
498 | ```go | |
499 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"} | |
500 | ``` | |
501 | ||
502 | ### Map To Struct | |
503 | ||
504 | Want more objective way to play with INI? Cool. | |
505 | ||
506 | ```ini | |
507 | Name = Unknwon | |
508 | age = 21 | |
509 | Male = true | |
510 | Born = 1993-01-01T20:17:05Z | |
511 | ||
512 | [Note] | |
513 | Content = Hi is a good man! | |
514 | Cities = HangZhou, Boston | |
515 | ``` | |
516 | ||
517 | ```go | |
518 | type Note struct { | |
519 | Content string | |
520 | Cities []string | |
521 | } | |
522 | ||
523 | type Person struct { | |
524 | Name string | |
525 | Age int `ini:"age"` | |
526 | Male bool | |
527 | Born time.Time | |
528 | Note | |
529 | Created time.Time `ini:"-"` | |
530 | } | |
531 | ||
532 | func main() { | |
533 | cfg, err := ini.Load("path/to/ini") | |
534 | // ... | |
535 | p := new(Person) | |
536 | err = cfg.MapTo(p) | |
537 | // ... | |
538 | ||
539 | // Things can be simpler. | |
540 | err = ini.MapTo(p, "path/to/ini") | |
541 | // ... | |
542 | ||
543 | // Just map a section? Fine. | |
544 | n := new(Note) | |
545 | err = cfg.Section("Note").MapTo(n) | |
546 | // ... | |
547 | } | |
548 | ``` | |
549 | ||
550 | Can I have default value for field? Absolutely. | |
551 | ||
552 | Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type. | |
553 | ||
554 | ```go | |
555 | // ... | |
556 | p := &Person{ | |
557 | Name: "Joe", | |
558 | } | |
559 | // ... | |
560 | ``` | |
561 | ||
562 | It's really cool, but what's the point if you can't give me my file back from struct? | |
563 | ||
564 | ### Reflect From Struct | |
565 | ||
566 | Why not? | |
567 | ||
568 | ```go | |
569 | type Embeded struct { | |
570 | Dates []time.Time `delim:"|"` | |
571 | Places []string `ini:"places,omitempty"` | |
572 | None []int `ini:",omitempty"` | |
573 | } | |
574 | ||
575 | type Author struct { | |
576 | Name string `ini:"NAME"` | |
577 | Male bool | |
578 | Age int | |
579 | GPA float64 | |
580 | NeverMind string `ini:"-"` | |
581 | *Embeded | |
582 | } | |
583 | ||
584 | func main() { | |
585 | a := &Author{"Unknwon", true, 21, 2.8, "", | |
586 | &Embeded{ | |
587 | []time.Time{time.Now(), time.Now()}, | |
588 | []string{"HangZhou", "Boston"}, | |
589 | []int{}, | |
590 | }} | |
591 | cfg := ini.Empty() | |
592 | err = ini.ReflectFrom(cfg, a) | |
593 | // ... | |
594 | } | |
595 | ``` | |
596 | ||
597 | So, what do I get? | |
598 | ||
599 | ```ini | |
600 | NAME = Unknwon | |
601 | Male = true | |
602 | Age = 21 | |
603 | GPA = 2.8 | |
604 | ||
605 | [Embeded] | |
606 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00 | |
607 | places = HangZhou,Boston | |
608 | ``` | |
609 | ||
610 | #### Name Mapper | |
611 | ||
612 | To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name. | |
613 | ||
614 | There are 2 built-in name mappers: | |
615 | ||
616 | - `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key. | |
617 | - `TitleUnderscore`: it converts to format `title_underscore` then match section or key. | |
618 | ||
619 | To use them: | |
620 | ||
621 | ```go | |
622 | type Info struct { | |
623 | PackageName string | |
624 | } | |
625 | ||
626 | func main() { | |
627 | err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini")) | |
628 | // ... | |
629 | ||
630 | cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) | |
631 | // ... | |
632 | info := new(Info) | |
633 | cfg.NameMapper = ini.AllCapsUnderscore | |
634 | err = cfg.MapTo(info) | |
635 | // ... | |
636 | } | |
637 | ``` | |
638 | ||
639 | Same rules of name mapper apply to `ini.ReflectFromWithMapper` function. | |
640 | ||
641 | #### Value Mapper | |
642 | ||
643 | To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values: | |
644 | ||
645 | ```go | |
646 | type Env struct { | |
647 | Foo string `ini:"foo"` | |
648 | } | |
649 | ||
650 | func main() { | |
651 | cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n") | |
652 | cfg.ValueMapper = os.ExpandEnv | |
653 | // ... | |
654 | env := &Env{} | |
655 | err = cfg.Section("env").MapTo(env) | |
656 | } | |
657 | ``` | |
658 | ||
659 | This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`. | |
660 | ||
661 | #### Other Notes On Map/Reflect | |
662 | ||
663 | Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature: | |
664 | ||
665 | ```go | |
666 | type Child struct { | |
667 | Age string | |
668 | } | |
669 | ||
670 | type Parent struct { | |
671 | Name string | |
672 | Child | |
673 | } | |
674 | ||
675 | type Config struct { | |
676 | City string | |
677 | Parent | |
678 | } | |
679 | ``` | |
680 | ||
681 | Example configuration: | |
682 | ||
683 | ```ini | |
684 | City = Boston | |
685 | ||
686 | [Parent] | |
687 | Name = Unknwon | |
688 | ||
689 | [Child] | |
690 | Age = 21 | |
691 | ``` | |
692 | ||
693 | What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome. | |
694 | ||
695 | ```go | |
696 | type Child struct { | |
697 | Age string | |
698 | } | |
699 | ||
700 | type Parent struct { | |
701 | Name string | |
702 | Child `ini:"Parent"` | |
703 | } | |
704 | ||
705 | type Config struct { | |
706 | City string | |
707 | Parent | |
708 | } | |
709 | ``` | |
710 | ||
711 | Example configuration: | |
712 | ||
713 | ```ini | |
714 | City = Boston | |
715 | ||
716 | [Parent] | |
717 | Name = Unknwon | |
718 | Age = 21 | |
719 | ``` | |
720 | ||
721 | ## Getting Help | |
722 | ||
723 | - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) | |
724 | - [File An Issue](https://github.com/go-ini/ini/issues/new) | |
725 | ||
726 | ## FAQs | |
727 | ||
728 | ### What does `BlockMode` field do? | |
729 | ||
730 | By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster. | |
731 | ||
732 | ### Why another INI library? | |
733 | ||
734 | Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster. | |
735 | ||
736 | To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path) | |
737 | ||
738 | ## License | |
739 | ||
740 | This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. |