• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..04-Dec-2018-

LICENSEH A D04-Dec-201810 KiB192155

README.mdH A D04-Dec-201816.7 KiB710517

README_ZH.mdH A D04-Dec-201817 KiB697510

error.goH A D04-Dec-2018883 3314

ini.goH A D04-Dec-201812.5 KiB502353

key.goH A D04-Dec-201818.3 KiB634435

parser.goH A D04-Dec-20187.2 KiB326247

section.goH A D04-Dec-20184.5 KiB207152

struct.goH A D04-Dec-201811.5 KiB432348

README.md

1INI [![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
6Package ini provides INI file read and write functionality in Go.
7
8[简体中文](README_ZH.md)
9
10## Feature
11
12- Load multiple data sources(`[]byte` or file) with overwrites.
13- Read with recursion values.
14- Read with parent-child sections.
15- Read with auto-increment key names.
16- Read with multiple-line values.
17- Read with tons of helper methods.
18- Read and convert values to Go types.
19- Read and **WRITE** comments of sections and keys.
20- Manipulate sections, keys and comments with ease.
21- Keep sections and keys in order as you parse and save.
22
23## Installation
24
25To use a tagged revision:
26
27	go get gopkg.in/ini.v1
28
29To use with latest changes:
30
31	go get github.com/go-ini/ini
32
33Please add `-u` flag to update in the future.
34
35### Testing
36
37If you want to test on your machine, please apply `-t` flag:
38
39	go get -t gopkg.in/ini.v1
40
41Please add `-u` flag to update in the future.
42
43## Getting Started
44
45### Loading from data sources
46
47A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many data sources as you want**. Passing other types will simply return an error.
48
49```go
50cfg, err := ini.Load([]byte("raw data"), "filename")
51```
52
53Or start with an empty object:
54
55```go
56cfg := ini.Empty()
57```
58
59When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
60
61```go
62err := cfg.Append("other file", []byte("other raw data"))
63```
64
65If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
66
67```go
68cfg, err := ini.LooseLoad("filename", "filename_404")
69```
70
71The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
72
73#### Ignore cases of key name
74
75When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
76
77```go
78cfg, err := ini.InsensitiveLoad("filename")
79//...
80
81// sec1 and sec2 are the exactly same section object
82sec1, err := cfg.GetSection("Section")
83sec2, err := cfg.GetSection("SecTIOn")
84
85// key1 and key2 are the exactly same key object
86key1, err := cfg.GetKey("Key")
87key2, err := cfg.GetKey("KeY")
88```
89
90#### MySQL-like boolean key
91
92MySQL's configuration allows a key without value as follows:
93
94```ini
95[mysqld]
96...
97skip-host-cache
98skip-name-resolve
99```
100
101By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
102
103```go
104cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
105```
106
107The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
108
109### Working with sections
110
111To get a section, you would need to:
112
113```go
114section, err := cfg.GetSection("section name")
115```
116
117For a shortcut for default section, just give an empty string as name:
118
119```go
120section, err := cfg.GetSection("")
121```
122
123When you're pretty sure the section exists, following code could make your life easier:
124
125```go
126section := cfg.Section("")
127```
128
129What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
130
131To create a new section:
132
133```go
134err := cfg.NewSection("new section")
135```
136
137To get a list of sections or section names:
138
139```go
140sections := cfg.Sections()
141names := cfg.SectionStrings()
142```
143
144### Working with keys
145
146To get a key under a section:
147
148```go
149key, err := cfg.Section("").GetKey("key name")
150```
151
152Same rule applies to key operations:
153
154```go
155key := cfg.Section("").Key("key name")
156```
157
158To check if a key exists:
159
160```go
161yes := cfg.Section("").HasKey("key name")
162```
163
164To create a new key:
165
166```go
167err := cfg.Section("").NewKey("name", "value")
168```
169
170To get a list of keys or key names:
171
172```go
173keys := cfg.Section("").Keys()
174names := cfg.Section("").KeyStrings()
175```
176
177To get a clone hash of keys and corresponding values:
178
179```go
180hash := cfg.Section("").KeysHash()
181```
182
183### Working with values
184
185To get a string value:
186
187```go
188val := cfg.Section("").Key("key name").String()
189```
190
191To validate key value on the fly:
192
193```go
194val := cfg.Section("").Key("key name").Validate(func(in string) string {
195	if len(in) == 0 {
196		return "default"
197	}
198	return in
199})
200```
201
202If 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):
203
204```go
205val := cfg.Section("").Key("key name").Value()
206```
207
208To check if raw value exists:
209
210```go
211yes := cfg.Section("").HasValue("test value")
212```
213
214To get value with types:
215
216```go
217// For boolean values:
218// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
219// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
220v, err = cfg.Section("").Key("BOOL").Bool()
221v, err = cfg.Section("").Key("FLOAT64").Float64()
222v, err = cfg.Section("").Key("INT").Int()
223v, err = cfg.Section("").Key("INT64").Int64()
224v, err = cfg.Section("").Key("UINT").Uint()
225v, err = cfg.Section("").Key("UINT64").Uint64()
226v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
227v, err = cfg.Section("").Key("TIME").Time() // RFC3339
228
229v = cfg.Section("").Key("BOOL").MustBool()
230v = cfg.Section("").Key("FLOAT64").MustFloat64()
231v = cfg.Section("").Key("INT").MustInt()
232v = cfg.Section("").Key("INT64").MustInt64()
233v = cfg.Section("").Key("UINT").MustUint()
234v = cfg.Section("").Key("UINT64").MustUint64()
235v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
236v = cfg.Section("").Key("TIME").MustTime() // RFC3339
237
238// Methods start with Must also accept one argument for default value
239// when key not found or fail to parse value to given type.
240// Except method MustString, which you have to pass a default value.
241
242v = cfg.Section("").Key("String").MustString("default")
243v = cfg.Section("").Key("BOOL").MustBool(true)
244v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
245v = cfg.Section("").Key("INT").MustInt(10)
246v = cfg.Section("").Key("INT64").MustInt64(99)
247v = cfg.Section("").Key("UINT").MustUint(3)
248v = cfg.Section("").Key("UINT64").MustUint64(6)
249v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
250v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
251```
252
253What if my value is three-line long?
254
255```ini
256[advance]
257ADDRESS = """404 road,
258NotFound, State, 5000
259Earth"""
260```
261
262Not a problem!
263
264```go
265cfg.Section("advance").Key("ADDRESS").String()
266
267/* --- start ---
268404 road,
269NotFound, State, 5000
270Earth
271------  end  --- */
272```
273
274That's cool, how about continuation lines?
275
276```ini
277[advance]
278two_lines = how about \
279	continuation lines?
280lots_of_lines = 1 \
281	2 \
282	3 \
283	4
284```
285
286Piece of cake!
287
288```go
289cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
290cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
291```
292
293Well, I hate continuation lines, how do I disable that?
294
295```go
296cfg, err := ini.LoadSources(ini.LoadOptions{
297	IgnoreContinuation: true,
298}, "filename")
299```
300
301Holy crap!
302
303Note that single quotes around values will be stripped:
304
305```ini
306foo = "some value" // foo: some value
307bar = 'some value' // bar: some value
308```
309
310That's all? Hmm, no.
311
312#### Helper methods of working with values
313
314To get value with given candidates:
315
316```go
317v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
318v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
319v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
320v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
321v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
322v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
323v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
324v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
325```
326
327Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
328
329To validate value in a given range:
330
331```go
332vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
333vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
334vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
335vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
336vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
337vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
338vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
339```
340
341##### Auto-split values into a slice
342
343To use zero value of type for invalid inputs:
344
345```go
346// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
347// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
348vals = cfg.Section("").Key("STRINGS").Strings(",")
349vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
350vals = cfg.Section("").Key("INTS").Ints(",")
351vals = cfg.Section("").Key("INT64S").Int64s(",")
352vals = cfg.Section("").Key("UINTS").Uints(",")
353vals = cfg.Section("").Key("UINT64S").Uint64s(",")
354vals = cfg.Section("").Key("TIMES").Times(",")
355```
356
357To exclude invalid values out of result slice:
358
359```go
360// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
361// Input: how, 2.2, are, you -> [2.2]
362vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
363vals = cfg.Section("").Key("INTS").ValidInts(",")
364vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
365vals = cfg.Section("").Key("UINTS").ValidUints(",")
366vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
367vals = cfg.Section("").Key("TIMES").ValidTimes(",")
368```
369
370Or to return nothing but error when have invalid inputs:
371
372```go
373// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
374// Input: how, 2.2, are, you -> error
375vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
376vals = cfg.Section("").Key("INTS").StrictInts(",")
377vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
378vals = cfg.Section("").Key("UINTS").StrictUints(",")
379vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
380vals = cfg.Section("").Key("TIMES").StrictTimes(",")
381```
382
383### Save your configuration
384
385Finally, it's time to save your configuration to somewhere.
386
387A typical way to save configuration is writing it to a file:
388
389```go
390// ...
391err = cfg.SaveTo("my.ini")
392err = cfg.SaveToIndent("my.ini", "\t")
393```
394
395Another way to save is writing to a `io.Writer` interface:
396
397```go
398// ...
399cfg.WriteTo(writer)
400cfg.WriteToIndent(writer, "\t")
401```
402
403By default, spaces are used to align "=" sign between key and values, to disable that:
404
405```go
406ini.PrettyFormat = false
407```
408
409## Advanced Usage
410
411### Recursive Values
412
413For 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.
414
415```ini
416NAME = ini
417
418[author]
419NAME = Unknwon
420GITHUB = https://github.com/%(NAME)s
421
422[package]
423FULL_NAME = github.com/go-ini/%(NAME)s
424```
425
426```go
427cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
428cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
429```
430
431### Parent-child Sections
432
433You 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.
434
435```ini
436NAME = ini
437VERSION = v1
438IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
439
440[package]
441CLONE_URL = https://%(IMPORT_PATH)s
442
443[package.sub]
444```
445
446```go
447cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
448```
449
450#### Retrieve parent keys available to a child section
451
452```go
453cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
454```
455
456### Auto-increment Key Names
457
458If 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.
459
460```ini
461[features]
462-: Support read/write comments of keys and sections
463-: Support auto-increment of key names
464-: Support load multiple files to overwrite key values
465```
466
467```go
468cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
469```
470
471### Map To Struct
472
473Want more objective way to play with INI? Cool.
474
475```ini
476Name = Unknwon
477age = 21
478Male = true
479Born = 1993-01-01T20:17:05Z
480
481[Note]
482Content = Hi is a good man!
483Cities = HangZhou, Boston
484```
485
486```go
487type Note struct {
488	Content string
489	Cities  []string
490}
491
492type Person struct {
493	Name string
494	Age  int `ini:"age"`
495	Male bool
496	Born time.Time
497	Note
498	Created time.Time `ini:"-"`
499}
500
501func main() {
502	cfg, err := ini.Load("path/to/ini")
503	// ...
504	p := new(Person)
505	err = cfg.MapTo(p)
506	// ...
507
508	// Things can be simpler.
509	err = ini.MapTo(p, "path/to/ini")
510	// ...
511
512	// Just map a section? Fine.
513	n := new(Note)
514	err = cfg.Section("Note").MapTo(n)
515	// ...
516}
517```
518
519Can I have default value for field? Absolutely.
520
521Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
522
523```go
524// ...
525p := &Person{
526	Name: "Joe",
527}
528// ...
529```
530
531It's really cool, but what's the point if you can't give me my file back from struct?
532
533### Reflect From Struct
534
535Why not?
536
537```go
538type Embeded struct {
539	Dates  []time.Time `delim:"|"`
540	Places []string    `ini:"places,omitempty"`
541	None   []int       `ini:",omitempty"`
542}
543
544type Author struct {
545	Name      string `ini:"NAME"`
546	Male      bool
547	Age       int
548	GPA       float64
549	NeverMind string `ini:"-"`
550	*Embeded
551}
552
553func main() {
554	a := &Author{"Unknwon", true, 21, 2.8, "",
555		&Embeded{
556			[]time.Time{time.Now(), time.Now()},
557			[]string{"HangZhou", "Boston"},
558			[]int{},
559		}}
560	cfg := ini.Empty()
561	err = ini.ReflectFrom(cfg, a)
562	// ...
563}
564```
565
566So, what do I get?
567
568```ini
569NAME = Unknwon
570Male = true
571Age = 21
572GPA = 2.8
573
574[Embeded]
575Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
576places = HangZhou,Boston
577```
578
579#### Name Mapper
580
581To 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.
582
583There are 2 built-in name mappers:
584
585- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
586- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
587
588To use them:
589
590```go
591type Info struct {
592	PackageName string
593}
594
595func main() {
596	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
597	// ...
598
599	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
600	// ...
601	info := new(Info)
602	cfg.NameMapper = ini.AllCapsUnderscore
603	err = cfg.MapTo(info)
604	// ...
605}
606```
607
608Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
609
610#### Value Mapper
611
612To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
613
614```go
615type Env struct {
616	Foo string `ini:"foo"`
617}
618
619func main() {
620	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
621	cfg.ValueMapper = os.ExpandEnv
622	// ...
623	env := &Env{}
624	err = cfg.Section("env").MapTo(env)
625}
626```
627
628This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
629
630#### Other Notes On Map/Reflect
631
632Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
633
634```go
635type Child struct {
636	Age string
637}
638
639type Parent struct {
640	Name string
641	Child
642}
643
644type Config struct {
645	City string
646	Parent
647}
648```
649
650Example configuration:
651
652```ini
653City = Boston
654
655[Parent]
656Name = Unknwon
657
658[Child]
659Age = 21
660```
661
662What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
663
664```go
665type Child struct {
666	Age string
667}
668
669type Parent struct {
670	Name string
671	Child `ini:"Parent"`
672}
673
674type Config struct {
675	City string
676	Parent
677}
678```
679
680Example configuration:
681
682```ini
683City = Boston
684
685[Parent]
686Name = Unknwon
687Age = 21
688```
689
690## Getting Help
691
692- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
693- [File An Issue](https://github.com/go-ini/ini/issues/new)
694
695## FAQs
696
697### What does `BlockMode` field do?
698
699By 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.
700
701### Why another INI library?
702
703Many 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.
704
705To 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)
706
707## License
708
709This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
710

README_ZH.md

1本包提供了 Go 语言中读写 INI 文件的功能。
2
3## 功能特性
4
5- 支持覆盖加载多个数据源(`[]byte` 或文件)
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` 类型的文件路径。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
41
42```go
43cfg, err := ini.Load([]byte("raw data"), "filename")
44```
45
46或者从一个空白的文件开始:
47
48```go
49cfg := ini.Empty()
50```
51
52当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
53
54```go
55err := cfg.Append("other file", []byte("other raw data"))
56```
57
58当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
59
60```go
61cfg, err := ini.LooseLoad("filename", "filename_404")
62```
63
64更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
65
66#### 忽略键名的大小写
67
68有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
69
70```go
71cfg, err := ini.InsensitiveLoad("filename")
72//...
73
74// sec1 和 sec2 指向同一个分区对象
75sec1, err := cfg.GetSection("Section")
76sec2, err := cfg.GetSection("SecTIOn")
77
78// key1 和 key2 指向同一个键对象
79key1, err := cfg.GetKey("Key")
80key2, err := cfg.GetKey("KeY")
81```
82
83#### 类似 MySQL 配置中的布尔值键
84
85MySQL 的配置文件中会出现没有具体值的布尔类型的键:
86
87```ini
88[mysqld]
89...
90skip-host-cache
91skip-name-resolve
92```
93
94默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
95
96```go
97cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
98```
99
100这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
101
102### 操作分区(Section)
103
104获取指定分区:
105
106```go
107section, err := cfg.GetSection("section name")
108```
109
110如果您想要获取默认分区,则可以用空字符串代替分区名:
111
112```go
113section, err := cfg.GetSection("")
114```
115
116当您非常确定某个分区是存在的,可以使用以下简便方法:
117
118```go
119section := cfg.Section("")
120```
121
122如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
123
124创建一个分区:
125
126```go
127err := cfg.NewSection("new section")
128```
129
130获取所有分区对象或名称:
131
132```go
133sections := cfg.Sections()
134names := cfg.SectionStrings()
135```
136
137### 操作键(Key)
138
139获取某个分区下的键:
140
141```go
142key, err := cfg.Section("").GetKey("key name")
143```
144
145和分区一样,您也可以直接获取键而忽略错误处理:
146
147```go
148key := cfg.Section("").Key("key name")
149```
150
151判断某个键是否存在:
152
153```go
154yes := cfg.Section("").HasKey("key name")
155```
156
157创建一个新的键:
158
159```go
160err := cfg.Section("").NewKey("name", "value")
161```
162
163获取分区下的所有键或键名:
164
165```go
166keys := cfg.Section("").Keys()
167names := cfg.Section("").KeyStrings()
168```
169
170获取分区下的所有键值对的克隆:
171
172```go
173hash := cfg.Section("").KeysHash()
174```
175
176### 操作键值(Value)
177
178获取一个类型为字符串(string)的值:
179
180```go
181val := cfg.Section("").Key("key name").String()
182```
183
184获取值的同时通过自定义函数进行处理验证:
185
186```go
187val := cfg.Section("").Key("key name").Validate(func(in string) string {
188	if len(in) == 0 {
189		return "default"
190	}
191	return in
192})
193```
194
195如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
196
197```go
198val := cfg.Section("").Key("key name").Value()
199```
200
201判断某个原值是否存在:
202
203```go
204yes := cfg.Section("").HasValue("test value")
205```
206
207获取其它类型的值:
208
209```go
210// 布尔值的规则:
211// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
212// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
213v, err = cfg.Section("").Key("BOOL").Bool()
214v, err = cfg.Section("").Key("FLOAT64").Float64()
215v, err = cfg.Section("").Key("INT").Int()
216v, err = cfg.Section("").Key("INT64").Int64()
217v, err = cfg.Section("").Key("UINT").Uint()
218v, err = cfg.Section("").Key("UINT64").Uint64()
219v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
220v, err = cfg.Section("").Key("TIME").Time() // RFC3339
221
222v = cfg.Section("").Key("BOOL").MustBool()
223v = cfg.Section("").Key("FLOAT64").MustFloat64()
224v = cfg.Section("").Key("INT").MustInt()
225v = cfg.Section("").Key("INT64").MustInt64()
226v = cfg.Section("").Key("UINT").MustUint()
227v = cfg.Section("").Key("UINT64").MustUint64()
228v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
229v = cfg.Section("").Key("TIME").MustTime() // RFC3339
230
231// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
232// 当键不存在或者转换失败时,则会直接返回该默认值。
233// 但是,MustString 方法必须传递一个默认值。
234
235v = cfg.Seciont("").Key("String").MustString("default")
236v = cfg.Section("").Key("BOOL").MustBool(true)
237v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
238v = cfg.Section("").Key("INT").MustInt(10)
239v = cfg.Section("").Key("INT64").MustInt64(99)
240v = cfg.Section("").Key("UINT").MustUint(3)
241v = cfg.Section("").Key("UINT64").MustUint64(6)
242v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
243v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
244```
245
246如果我的值有好多行怎么办?
247
248```ini
249[advance]
250ADDRESS = """404 road,
251NotFound, State, 5000
252Earth"""
253```
254
255嗯哼?小 case!
256
257```go
258cfg.Section("advance").Key("ADDRESS").String()
259
260/* --- start ---
261404 road,
262NotFound, State, 5000
263Earth
264------  end  --- */
265```
266
267赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
268
269```ini
270[advance]
271two_lines = how about \
272	continuation lines?
273lots_of_lines = 1 \
274	2 \
275	3 \
276	4
277```
278
279简直是小菜一碟!
280
281```go
282cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
283cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
284```
285
286可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
287
288```go
289cfg, err := ini.LoadSources(ini.LoadOptions{
290	IgnoreContinuation: true,
291}, "filename")
292```
293
294哇靠给力啊!
295
296需要注意的是,值两侧的单引号会被自动剔除:
297
298```ini
299foo = "some value" // foo: some value
300bar = 'some value' // bar: some value
301```
302
303这就是全部了?哈哈,当然不是。
304
305#### 操作键值的辅助方法
306
307获取键值时设定候选值:
308
309```go
310v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
311v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
312v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
313v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
314v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
315v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
316v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
317v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
318```
319
320如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
321
322验证获取的值是否在指定范围内:
323
324```go
325vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
326vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
327vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
328vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
329vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
330vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
331vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
332```
333
334##### 自动分割键值到切片(slice)
335
336当存在无效输入时,使用零值代替:
337
338```go
339// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
340// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
341vals = cfg.Section("").Key("STRINGS").Strings(",")
342vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
343vals = cfg.Section("").Key("INTS").Ints(",")
344vals = cfg.Section("").Key("INT64S").Int64s(",")
345vals = cfg.Section("").Key("UINTS").Uints(",")
346vals = cfg.Section("").Key("UINT64S").Uint64s(",")
347vals = cfg.Section("").Key("TIMES").Times(",")
348```
349
350从结果切片中剔除无效输入:
351
352```go
353// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
354// Input: how, 2.2, are, you -> [2.2]
355vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
356vals = cfg.Section("").Key("INTS").ValidInts(",")
357vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
358vals = cfg.Section("").Key("UINTS").ValidUints(",")
359vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
360vals = cfg.Section("").Key("TIMES").ValidTimes(",")
361```
362
363当存在无效输入时,直接返回错误:
364
365```go
366// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
367// Input: how, 2.2, are, you -> error
368vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
369vals = cfg.Section("").Key("INTS").StrictInts(",")
370vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
371vals = cfg.Section("").Key("UINTS").StrictUints(",")
372vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
373vals = cfg.Section("").Key("TIMES").StrictTimes(",")
374```
375
376### 保存配置
377
378终于到了这个时刻,是时候保存一下配置了。
379
380比较原始的做法是输出配置到某个文件:
381
382```go
383// ...
384err = cfg.SaveTo("my.ini")
385err = cfg.SaveToIndent("my.ini", "\t")
386```
387
388另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
389
390```go
391// ...
392cfg.WriteTo(writer)
393cfg.WriteToIndent(writer, "\t")
394```
395
396默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
397
398```go
399ini.PrettyFormat = false
400```
401
402### 高级用法
403
404#### 递归读取键值
405
406在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
407
408```ini
409NAME = ini
410
411[author]
412NAME = Unknwon
413GITHUB = https://github.com/%(NAME)s
414
415[package]
416FULL_NAME = github.com/go-ini/%(NAME)s
417```
418
419```go
420cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
421cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
422```
423
424#### 读取父子分区
425
426您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
427
428```ini
429NAME = ini
430VERSION = v1
431IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
432
433[package]
434CLONE_URL = https://%(IMPORT_PATH)s
435
436[package.sub]
437```
438
439```go
440cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
441```
442
443#### 获取上级父分区下的所有键名
444
445```go
446cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
447```
448
449#### 读取自增键名
450
451如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
452
453```ini
454[features]
455-: Support read/write comments of keys and sections
456-: Support auto-increment of key names
457-: Support load multiple files to overwrite key values
458```
459
460```go
461cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
462```
463
464### 映射到结构
465
466想要使用更加面向对象的方式玩转 INI 吗?好主意。
467
468```ini
469Name = Unknwon
470age = 21
471Male = true
472Born = 1993-01-01T20:17:05Z
473
474[Note]
475Content = Hi is a good man!
476Cities = HangZhou, Boston
477```
478
479```go
480type Note struct {
481	Content string
482	Cities  []string
483}
484
485type Person struct {
486	Name string
487	Age  int `ini:"age"`
488	Male bool
489	Born time.Time
490	Note
491	Created time.Time `ini:"-"`
492}
493
494func main() {
495	cfg, err := ini.Load("path/to/ini")
496	// ...
497	p := new(Person)
498	err = cfg.MapTo(p)
499	// ...
500
501	// 一切竟可以如此的简单。
502	err = ini.MapTo(p, "path/to/ini")
503	// ...
504
505	// 嗯哼?只需要映射一个分区吗?
506	n := new(Note)
507	err = cfg.Section("Note").MapTo(n)
508	// ...
509}
510```
511
512结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
513
514```go
515// ...
516p := &Person{
517	Name: "Joe",
518}
519// ...
520```
521
522这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
523
524### 从结构反射
525
526可是,我有说不能吗?
527
528```go
529type Embeded struct {
530	Dates  []time.Time `delim:"|"`
531	Places []string    `ini:"places,omitempty"`
532	None   []int       `ini:",omitempty"`
533}
534
535type Author struct {
536	Name      string `ini:"NAME"`
537	Male      bool
538	Age       int
539	GPA       float64
540	NeverMind string `ini:"-"`
541	*Embeded
542}
543
544func main() {
545	a := &Author{"Unknwon", true, 21, 2.8, "",
546		&Embeded{
547			[]time.Time{time.Now(), time.Now()},
548			[]string{"HangZhou", "Boston"},
549			[]int{},
550		}}
551	cfg := ini.Empty()
552	err = ini.ReflectFrom(cfg, a)
553	// ...
554}
555```
556
557瞧瞧,奇迹发生了。
558
559```ini
560NAME = Unknwon
561Male = true
562Age = 21
563GPA = 2.8
564
565[Embeded]
566Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
567places = HangZhou,Boston
568```
569
570#### 名称映射器(Name Mapper)
571
572为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
573
574目前有 2 款内置的映射器:
575
576- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
577- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
578
579使用方法:
580
581```go
582type Info struct{
583	PackageName string
584}
585
586func main() {
587	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
588	// ...
589
590	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
591	// ...
592	info := new(Info)
593	cfg.NameMapper = ini.AllCapsUnderscore
594	err = cfg.MapTo(info)
595	// ...
596}
597```
598
599使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
600
601#### 值映射器(Value Mapper)
602
603值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
604
605```go
606type Env struct {
607	Foo string `ini:"foo"`
608}
609
610func main() {
611	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
612	cfg.ValueMapper = os.ExpandEnv
613	// ...
614	env := &Env{}
615	err = cfg.Section("env").MapTo(env)
616}
617```
618
619本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
620
621#### 映射/反射的其它说明
622
623任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
624
625```go
626type Child struct {
627	Age string
628}
629
630type Parent struct {
631	Name string
632	Child
633}
634
635type Config struct {
636	City string
637	Parent
638}
639```
640
641示例配置文件:
642
643```ini
644City = Boston
645
646[Parent]
647Name = Unknwon
648
649[Child]
650Age = 21
651```
652
653很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
654
655```go
656type Child struct {
657	Age string
658}
659
660type Parent struct {
661	Name string
662	Child `ini:"Parent"`
663}
664
665type Config struct {
666	City string
667	Parent
668}
669```
670
671示例配置文件:
672
673```ini
674City = Boston
675
676[Parent]
677Name = Unknwon
678Age = 21
679```
680
681## 获取帮助
682
683- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
684- [创建工单](https://github.com/go-ini/ini/issues/new)
685
686## 常见问题
687
688### 字段 `BlockMode` 是什么?
689
690默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
691
692### 为什么要写另一个 INI 解析库?
693
694许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
695
696为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
697