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

..10-Jul-2019-

.gitignoreH A D10-Jul-201996 65

.travis.ymlH A D10-Jul-2019244 1815

LICENSEH A D10-Jul-201910 KiB192155

MakefileH A D10-Jul-2019155 138

README.mdH A D10-Jul-201917.9 KiB741537

README_ZH.mdH A D10-Jul-201918.1 KiB728530

error.goH A D10-Jul-2019883 3314

ini.goH A D10-Jul-201913.9 KiB550387

key.goH A D10-Jul-201920 KiB704487

parser.goH A D10-Jul-20198 KiB359277

section.goH A D10-Jul-20195.2 KiB235173

struct.goH A D10-Jul-201912 KiB451365

README.md

1INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
2===
3
4![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
5
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`, 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
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`, 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
50cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
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
109To generate such keys in your program, you could use `NewBooleanKey`:
110
111```go
112key, err := sec.NewBooleanKey("skip-host-cache")
113```
114
115#### Comment
116
117Take care that following format will be treated as comment:
118
1191. Line begins with `#` or `;`
1202. Words after `#` or `;`
1213. Words after section name (i.e words after `[some section name]`)
122
123If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
124
125### Working with sections
126
127To get a section, you would need to:
128
129```go
130section, err := cfg.GetSection("section name")
131```
132
133For a shortcut for default section, just give an empty string as name:
134
135```go
136section, err := cfg.GetSection("")
137```
138
139When you're pretty sure the section exists, following code could make your life easier:
140
141```go
142section := cfg.Section("section name")
143```
144
145What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
146
147To create a new section:
148
149```go
150err := cfg.NewSection("new section")
151```
152
153To get a list of sections or section names:
154
155```go
156sections := cfg.Sections()
157names := cfg.SectionStrings()
158```
159
160### Working with keys
161
162To get a key under a section:
163
164```go
165key, err := cfg.Section("").GetKey("key name")
166```
167
168Same rule applies to key operations:
169
170```go
171key := cfg.Section("").Key("key name")
172```
173
174To check if a key exists:
175
176```go
177yes := cfg.Section("").HasKey("key name")
178```
179
180To create a new key:
181
182```go
183err := cfg.Section("").NewKey("name", "value")
184```
185
186To get a list of keys or key names:
187
188```go
189keys := cfg.Section("").Keys()
190names := cfg.Section("").KeyStrings()
191```
192
193To get a clone hash of keys and corresponding values:
194
195```go
196hash := cfg.Section("").KeysHash()
197```
198
199### Working with values
200
201To get a string value:
202
203```go
204val := cfg.Section("").Key("key name").String()
205```
206
207To validate key value on the fly:
208
209```go
210val := 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
218If 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
221val := cfg.Section("").Key("key name").Value()
222```
223
224To check if raw value exists:
225
226```go
227yes := cfg.Section("").HasValue("test value")
228```
229
230To 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
236v, err = cfg.Section("").Key("BOOL").Bool()
237v, err = cfg.Section("").Key("FLOAT64").Float64()
238v, err = cfg.Section("").Key("INT").Int()
239v, err = cfg.Section("").Key("INT64").Int64()
240v, err = cfg.Section("").Key("UINT").Uint()
241v, err = cfg.Section("").Key("UINT64").Uint64()
242v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
243v, err = cfg.Section("").Key("TIME").Time() // RFC3339
244
245v = cfg.Section("").Key("BOOL").MustBool()
246v = cfg.Section("").Key("FLOAT64").MustFloat64()
247v = cfg.Section("").Key("INT").MustInt()
248v = cfg.Section("").Key("INT64").MustInt64()
249v = cfg.Section("").Key("UINT").MustUint()
250v = cfg.Section("").Key("UINT64").MustUint64()
251v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
252v = 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
258v = cfg.Section("").Key("String").MustString("default")
259v = cfg.Section("").Key("BOOL").MustBool(true)
260v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
261v = cfg.Section("").Key("INT").MustInt(10)
262v = cfg.Section("").Key("INT64").MustInt64(99)
263v = cfg.Section("").Key("UINT").MustUint(3)
264v = cfg.Section("").Key("UINT64").MustUint64(6)
265v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
266v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
267```
268
269What if my value is three-line long?
270
271```ini
272[advance]
273ADDRESS = """404 road,
274NotFound, State, 5000
275Earth"""
276```
277
278Not a problem!
279
280```go
281cfg.Section("advance").Key("ADDRESS").String()
282
283/* --- start ---
284404 road,
285NotFound, State, 5000
286Earth
287------  end  --- */
288```
289
290That's cool, how about continuation lines?
291
292```ini
293[advance]
294two_lines = how about \
295	continuation lines?
296lots_of_lines = 1 \
297	2 \
298	3 \
299	4
300```
301
302Piece of cake!
303
304```go
305cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
306cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
307```
308
309Well, I hate continuation lines, how do I disable that?
310
311```go
312cfg, err := ini.LoadSources(ini.LoadOptions{
313	IgnoreContinuation: true,
314}, "filename")
315```
316
317Holy crap!
318
319Note that single quotes around values will be stripped:
320
321```ini
322foo = "some value" // foo: some value
323bar = 'some value' // bar: some value
324```
325
326That's all? Hmm, no.
327
328#### Helper methods of working with values
329
330To get value with given candidates:
331
332```go
333v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
334v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
335v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
336v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
337v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
338v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
339v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
340v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
341```
342
343Default 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
345To validate value in a given range:
346
347```go
348vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
349vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
350vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
351vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
352vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
353vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
354vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
355```
356
357##### Auto-split values into a slice
358
359To 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]
364vals = cfg.Section("").Key("STRINGS").Strings(",")
365vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
366vals = cfg.Section("").Key("INTS").Ints(",")
367vals = cfg.Section("").Key("INT64S").Int64s(",")
368vals = cfg.Section("").Key("UINTS").Uints(",")
369vals = cfg.Section("").Key("UINT64S").Uint64s(",")
370vals = cfg.Section("").Key("TIMES").Times(",")
371```
372
373To 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]
378vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
379vals = cfg.Section("").Key("INTS").ValidInts(",")
380vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
381vals = cfg.Section("").Key("UINTS").ValidUints(",")
382vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
383vals = cfg.Section("").Key("TIMES").ValidTimes(",")
384```
385
386Or 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
391vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
392vals = cfg.Section("").Key("INTS").StrictInts(",")
393vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
394vals = cfg.Section("").Key("UINTS").StrictUints(",")
395vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
396vals = cfg.Section("").Key("TIMES").StrictTimes(",")
397```
398
399### Save your configuration
400
401Finally, it's time to save your configuration to somewhere.
402
403A typical way to save configuration is writing it to a file:
404
405```go
406// ...
407err = cfg.SaveTo("my.ini")
408err = cfg.SaveToIndent("my.ini", "\t")
409```
410
411Another way to save is writing to a `io.Writer` interface:
412
413```go
414// ...
415cfg.WriteTo(writer)
416cfg.WriteToIndent(writer, "\t")
417```
418
419By default, spaces are used to align "=" sign between key and values, to disable that:
420
421```go
422ini.PrettyFormat = false
423```
424
425## Advanced Usage
426
427### Recursive Values
428
429For 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
432NAME = ini
433
434[author]
435NAME = Unknwon
436GITHUB = https://github.com/%(NAME)s
437
438[package]
439FULL_NAME = github.com/go-ini/%(NAME)s
440```
441
442```go
443cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
444cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
445```
446
447### Parent-child Sections
448
449You 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
452NAME = ini
453VERSION = v1
454IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
455
456[package]
457CLONE_URL = https://%(IMPORT_PATH)s
458
459[package.sub]
460```
461
462```go
463cfg.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
469cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
470```
471
472### Unparseable Sections
473
474Sometimes, 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
477cfg, 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
480body := 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
489If 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
499cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
500```
501
502### Map To Struct
503
504Want more objective way to play with INI? Cool.
505
506```ini
507Name = Unknwon
508age = 21
509Male = true
510Born = 1993-01-01T20:17:05Z
511
512[Note]
513Content = Hi is a good man!
514Cities = HangZhou, Boston
515```
516
517```go
518type Note struct {
519	Content string
520	Cities  []string
521}
522
523type 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
532func 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
550Can I have default value for field? Absolutely.
551
552Assign 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// ...
556p := &Person{
557	Name: "Joe",
558}
559// ...
560```
561
562It'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
566Why not?
567
568```go
569type Embeded struct {
570	Dates  []time.Time `delim:"|"`
571	Places []string    `ini:"places,omitempty"`
572	None   []int       `ini:",omitempty"`
573}
574
575type Author struct {
576	Name      string `ini:"NAME"`
577	Male      bool
578	Age       int
579	GPA       float64
580	NeverMind string `ini:"-"`
581	*Embeded
582}
583
584func 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
597So, what do I get?
598
599```ini
600NAME = Unknwon
601Male = true
602Age = 21
603GPA = 2.8
604
605[Embeded]
606Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
607places = HangZhou,Boston
608```
609
610#### Name Mapper
611
612To 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
614There 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
619To use them:
620
621```go
622type Info struct {
623	PackageName string
624}
625
626func 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
639Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
640
641#### Value Mapper
642
643To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
644
645```go
646type Env struct {
647	Foo string `ini:"foo"`
648}
649
650func 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
659This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
660
661#### Other Notes On Map/Reflect
662
663Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
664
665```go
666type Child struct {
667	Age string
668}
669
670type Parent struct {
671	Name string
672	Child
673}
674
675type Config struct {
676	City string
677	Parent
678}
679```
680
681Example configuration:
682
683```ini
684City = Boston
685
686[Parent]
687Name = Unknwon
688
689[Child]
690Age = 21
691```
692
693What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
694
695```go
696type Child struct {
697	Age string
698}
699
700type Parent struct {
701	Name string
702	Child `ini:"Parent"`
703}
704
705type Config struct {
706	City string
707	Parent
708}
709```
710
711Example configuration:
712
713```ini
714City = Boston
715
716[Parent]
717Name = Unknwon
718Age = 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
730By 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
734Many 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
736To 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
740This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
741

README_ZH.md

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