|  | @@ -1,15 +1,13 @@
 | 
	
		
			
				|  |  | -INI [](https://travis-ci.org/go-ini/ini) [](https://sourcegraph.com/github.com/go-ini/ini?badge)
 | 
	
		
			
				|  |  | +INI [](https://travis-ci.org/go-ini/ini) [](https://sourcegraph.com/github.com/go-ini/ini)
 | 
	
		
			
				|  |  |  ===
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  Package ini provides INI file read and write functionality in Go.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -[简体中文](README_ZH.md)
 | 
	
		
			
				|  |  | +## Features
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -## Feature
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
 | 
	
		
			
				|  |  | +- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
 | 
	
		
			
				|  |  |  - Read with recursion values.
 | 
	
		
			
				|  |  |  - Read with parent-child sections.
 | 
	
		
			
				|  |  |  - Read with auto-increment key names.
 | 
	
	
		
			
				|  | @@ -24,716 +22,22 @@ Package ini provides INI file read and write functionality in Go.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  To use a tagged revision:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	go get gopkg.in/ini.v1
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To use with latest changes:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	go get github.com/go-ini/ini
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Please add `-u` flag to update in the future.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Testing
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -If you want to test on your machine, please apply `-t` flag:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	go get -t gopkg.in/ini.v1
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Please add `-u` flag to update in the future.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -## Getting Started
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Loading from data sources
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Or start with an empty object:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg := ini.Empty()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -err := cfg.Append("other file", []byte("other raw data"))
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := ini.LooseLoad("filename", "filename_404")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Ignore cases of key name
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := ini.InsensitiveLoad("filename")
 | 
	
		
			
				|  |  | -//...
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// sec1 and sec2 are the exactly same section object
 | 
	
		
			
				|  |  | -sec1, err := cfg.GetSection("Section")
 | 
	
		
			
				|  |  | -sec2, err := cfg.GetSection("SecTIOn")
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// key1 and key2 are the exactly same key object
 | 
	
		
			
				|  |  | -key1, err := cfg.GetKey("Key")
 | 
	
		
			
				|  |  | -key2, err := cfg.GetKey("KeY")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### MySQL-like boolean key 
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -MySQL's configuration allows a key without value as follows:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -[mysqld]
 | 
	
		
			
				|  |  | -...
 | 
	
		
			
				|  |  | -skip-host-cache
 | 
	
		
			
				|  |  | -skip-name-resolve
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To generate such keys in your program, you could use `NewBooleanKey`:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -key, err := sec.NewBooleanKey("skip-host-cache")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Comment
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Take care that following format will be treated as comment:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -1. Line begins with `#` or `;`
 | 
	
		
			
				|  |  | -2. Words after `#` or `;`
 | 
	
		
			
				|  |  | -3. Words after section name (i.e words after `[some section name]`)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Working with sections
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a section, you would need to:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -section, err := cfg.GetSection("section name")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -For a shortcut for default section, just give an empty string as name:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -section, err := cfg.GetSection("")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -When you're pretty sure the section exists, following code could make your life easier:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -section := cfg.Section("section name")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To create a new section:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -err := cfg.NewSection("new section")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a list of sections or section names:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -sections := cfg.Sections()
 | 
	
		
			
				|  |  | -names := cfg.SectionStrings()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Working with keys
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a key under a section:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -key, err := cfg.Section("").GetKey("key name")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Same rule applies to key operations:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -key := cfg.Section("").Key("key name")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To check if a key exists:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -yes := cfg.Section("").HasKey("key name")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To create a new key:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -err := cfg.Section("").NewKey("name", "value")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a list of keys or key names:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -keys := cfg.Section("").Keys()
 | 
	
		
			
				|  |  | -names := cfg.Section("").KeyStrings()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a clone hash of keys and corresponding values:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -hash := cfg.Section("").KeysHash()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Working with values
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get a string value:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -val := cfg.Section("").Key("key name").String()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To validate key value on the fly:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -val := cfg.Section("").Key("key name").Validate(func(in string) string {
 | 
	
		
			
				|  |  | -	if len(in) == 0 {
 | 
	
		
			
				|  |  | -		return "default"
 | 
	
		
			
				|  |  | -	}
 | 
	
		
			
				|  |  | -	return in
 | 
	
		
			
				|  |  | -})
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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):
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -val := cfg.Section("").Key("key name").Value()
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To check if raw value exists:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -yes := cfg.Section("").HasValue("test value")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get value with types:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// For boolean values:
 | 
	
		
			
				|  |  | -// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
 | 
	
		
			
				|  |  | -// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("BOOL").Bool()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("FLOAT64").Float64()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("INT").Int()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("INT64").Int64()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("UINT").Uint()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("UINT64").Uint64()
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
 | 
	
		
			
				|  |  | -v, err = cfg.Section("").Key("TIME").Time() // RFC3339
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("BOOL").MustBool()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("FLOAT64").MustFloat64()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT").MustInt()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT64").MustInt64()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT").MustUint()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT64").MustUint64()
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").MustTime() // RFC3339
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -// Methods start with Must also accept one argument for default value
 | 
	
		
			
				|  |  | -// when key not found or fail to parse value to given type.
 | 
	
		
			
				|  |  | -// Except method MustString, which you have to pass a default value.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("String").MustString("default")
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("BOOL").MustBool(true)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT").MustInt(10)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT64").MustInt64(99)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT").MustUint(3)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT64").MustUint64(6)
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -What if my value is three-line long?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -[advance]
 | 
	
		
			
				|  |  | -ADDRESS = """404 road,
 | 
	
		
			
				|  |  | -NotFound, State, 5000
 | 
	
		
			
				|  |  | -Earth"""
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Not a problem!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("advance").Key("ADDRESS").String()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/* --- start ---
 | 
	
		
			
				|  |  | -404 road,
 | 
	
		
			
				|  |  | -NotFound, State, 5000
 | 
	
		
			
				|  |  | -Earth
 | 
	
		
			
				|  |  | -------  end  --- */
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -That's cool, how about continuation lines?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -[advance]
 | 
	
		
			
				|  |  | -two_lines = how about \
 | 
	
		
			
				|  |  | -	continuation lines?
 | 
	
		
			
				|  |  | -lots_of_lines = 1 \
 | 
	
		
			
				|  |  | -	2 \
 | 
	
		
			
				|  |  | -	3 \
 | 
	
		
			
				|  |  | -	4
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Piece of cake!
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
 | 
	
		
			
				|  |  | -cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Well, I hate continuation lines, how do I disable that?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := ini.LoadSources(ini.LoadOptions{
 | 
	
		
			
				|  |  | -	IgnoreContinuation: true,
 | 
	
		
			
				|  |  | -}, "filename")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Holy crap! 
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Note that single quotes around values will be stripped:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -foo = "some value" // foo: some value
 | 
	
		
			
				|  |  | -bar = 'some value' // bar: some value
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -That's all? Hmm, no.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Helper methods of working with values
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To get value with given candidates:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
 | 
	
		
			
				|  |  | -v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To validate value in a given range:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -##### Auto-split values into a slice
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To use zero value of type for invalid inputs:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
 | 
	
		
			
				|  |  | -// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("STRINGS").Strings(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INTS").Ints(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INT64S").Int64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINTS").Uints(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINT64S").Uint64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("TIMES").Times(",")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To exclude invalid values out of result slice:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
 | 
	
		
			
				|  |  | -// Input: how, 2.2, are, you -> [2.2]
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INTS").ValidInts(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINTS").ValidUints(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("TIMES").ValidTimes(",")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Or to return nothing but error when have invalid inputs:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
 | 
	
		
			
				|  |  | -// Input: how, 2.2, are, you -> error
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INTS").StrictInts(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINTS").StrictUints(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
 | 
	
		
			
				|  |  | -vals = cfg.Section("").Key("TIMES").StrictTimes(",")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Save your configuration
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Finally, it's time to save your configuration to somewhere.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -A typical way to save configuration is writing it to a file:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// ...
 | 
	
		
			
				|  |  | -err = cfg.SaveTo("my.ini")
 | 
	
		
			
				|  |  | -err = cfg.SaveToIndent("my.ini", "\t")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Another way to save is writing to a `io.Writer` interface:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// ...
 | 
	
		
			
				|  |  | -cfg.WriteTo(writer)
 | 
	
		
			
				|  |  | -cfg.WriteToIndent(writer, "\t")
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -By default, spaces are used to align "=" sign between key and values, to disable that:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -ini.PrettyFormat = false
 | 
	
		
			
				|  |  | -``` 
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -## Advanced Usage
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Recursive Values
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -NAME = ini
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[author]
 | 
	
		
			
				|  |  | -NAME = Unknwon
 | 
	
		
			
				|  |  | -GITHUB = https://github.com/%(NAME)s
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[package]
 | 
	
		
			
				|  |  | -FULL_NAME = github.com/go-ini/%(NAME)s
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
 | 
	
		
			
				|  |  | -cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Parent-child Sections
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -NAME = ini
 | 
	
		
			
				|  |  | -VERSION = v1
 | 
	
		
			
				|  |  | -IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[package]
 | 
	
		
			
				|  |  | -CLONE_URL = https://%(IMPORT_PATH)s
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[package.sub]
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Retrieve parent keys available to a child section
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Unparseable Sections
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
 | 
	
		
			
				|  |  | -<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -body := cfg.Section("COMMENTS").Body()
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -/* --- start ---
 | 
	
		
			
				|  |  | -<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
 | 
	
		
			
				|  |  | -------  end  --- */
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Auto-increment Key Names
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -[features]
 | 
	
		
			
				|  |  | --: Support read/write comments of keys and sections
 | 
	
		
			
				|  |  | --: Support auto-increment of key names
 | 
	
		
			
				|  |  | --: Support load multiple files to overwrite key values
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Map To Struct
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Want more objective way to play with INI? Cool.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -Name = Unknwon
 | 
	
		
			
				|  |  | -age = 21
 | 
	
		
			
				|  |  | -Male = true
 | 
	
		
			
				|  |  | -Born = 1993-01-01T20:17:05Z
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[Note]
 | 
	
		
			
				|  |  | -Content = Hi is a good man!
 | 
	
		
			
				|  |  | -Cities = HangZhou, Boston
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Note struct {
 | 
	
		
			
				|  |  | -	Content string
 | 
	
		
			
				|  |  | -	Cities  []string
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type Person struct {
 | 
	
		
			
				|  |  | -	Name string
 | 
	
		
			
				|  |  | -	Age  int `ini:"age"`
 | 
	
		
			
				|  |  | -	Male bool
 | 
	
		
			
				|  |  | -	Born time.Time
 | 
	
		
			
				|  |  | -	Note
 | 
	
		
			
				|  |  | -	Created time.Time `ini:"-"`
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func main() {
 | 
	
		
			
				|  |  | -	cfg, err := ini.Load("path/to/ini")
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -	p := new(Person)
 | 
	
		
			
				|  |  | -	err = cfg.MapTo(p)
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	// Things can be simpler.
 | 
	
		
			
				|  |  | -	err = ini.MapTo(p, "path/to/ini")
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	// Just map a section? Fine.
 | 
	
		
			
				|  |  | -	n := new(Note)
 | 
	
		
			
				|  |  | -	err = cfg.Section("Note").MapTo(n)
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Can I have default value for field? Absolutely.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -// ...
 | 
	
		
			
				|  |  | -p := &Person{
 | 
	
		
			
				|  |  | -	Name: "Joe",
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -// ...
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -It's really cool, but what's the point if you can't give me my file back from struct?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Reflect From Struct
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Why not?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Embeded struct {
 | 
	
		
			
				|  |  | -	Dates  []time.Time `delim:"|"`
 | 
	
		
			
				|  |  | -	Places []string    `ini:"places,omitempty"`
 | 
	
		
			
				|  |  | -	None   []int       `ini:",omitempty"`
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type Author struct {
 | 
	
		
			
				|  |  | -	Name      string `ini:"NAME"`
 | 
	
		
			
				|  |  | -	Male      bool
 | 
	
		
			
				|  |  | -	Age       int
 | 
	
		
			
				|  |  | -	GPA       float64
 | 
	
		
			
				|  |  | -	NeverMind string `ini:"-"`
 | 
	
		
			
				|  |  | -	*Embeded
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func main() {
 | 
	
		
			
				|  |  | -	a := &Author{"Unknwon", true, 21, 2.8, "",
 | 
	
		
			
				|  |  | -		&Embeded{
 | 
	
		
			
				|  |  | -			[]time.Time{time.Now(), time.Now()},
 | 
	
		
			
				|  |  | -			[]string{"HangZhou", "Boston"},
 | 
	
		
			
				|  |  | -			[]int{},
 | 
	
		
			
				|  |  | -		}}
 | 
	
		
			
				|  |  | -	cfg := ini.Empty()
 | 
	
		
			
				|  |  | -	err = ini.ReflectFrom(cfg, a)
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -So, what do I get?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -NAME = Unknwon
 | 
	
		
			
				|  |  | -Male = true
 | 
	
		
			
				|  |  | -Age = 21
 | 
	
		
			
				|  |  | -GPA = 2.8
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[Embeded]
 | 
	
		
			
				|  |  | -Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
 | 
	
		
			
				|  |  | -places = HangZhou,Boston
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Name Mapper
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -There are 2 built-in name mappers:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
 | 
	
		
			
				|  |  | -- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To use them:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Info struct {
 | 
	
		
			
				|  |  | -	PackageName string
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func main() {
 | 
	
		
			
				|  |  | -	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -	info := new(Info)
 | 
	
		
			
				|  |  | -	cfg.NameMapper = ini.AllCapsUnderscore
 | 
	
		
			
				|  |  | -	err = cfg.MapTo(info)
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +```sh
 | 
	
		
			
				|  |  | +$ go get gopkg.in/ini.v1
 | 
	
		
			
				|  |  |  ```
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Value Mapper
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Env struct {
 | 
	
		
			
				|  |  | -	Foo string `ini:"foo"`
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -func main() {
 | 
	
		
			
				|  |  | -	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
 | 
	
		
			
				|  |  | -	cfg.ValueMapper = os.ExpandEnv
 | 
	
		
			
				|  |  | -	// ...
 | 
	
		
			
				|  |  | -	env := &Env{}
 | 
	
		
			
				|  |  | -	err = cfg.Section("env").MapTo(env)
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#### Other Notes On Map/Reflect
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Child struct {
 | 
	
		
			
				|  |  | -	Age string
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type Parent struct {
 | 
	
		
			
				|  |  | -	Name string
 | 
	
		
			
				|  |  | -	Child
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type Config struct {
 | 
	
		
			
				|  |  | -	City string
 | 
	
		
			
				|  |  | -	Parent
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -Example configuration:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -City = Boston
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[Parent]
 | 
	
		
			
				|  |  | -Name = Unknwon
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[Child]
 | 
	
		
			
				|  |  | -Age = 21
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```go
 | 
	
		
			
				|  |  | -type Child struct {
 | 
	
		
			
				|  |  | -	Age string
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -type Parent struct {
 | 
	
		
			
				|  |  | -	Name string
 | 
	
		
			
				|  |  | -	Child `ini:"Parent"`
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +To use with latest changes:
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -type Config struct {
 | 
	
		
			
				|  |  | -	City string
 | 
	
		
			
				|  |  | -	Parent
 | 
	
		
			
				|  |  | -}
 | 
	
		
			
				|  |  | +```sh
 | 
	
		
			
				|  |  | +$ go get github.com/go-ini/ini
 | 
	
		
			
				|  |  |  ```
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -Example configuration:
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -```ini
 | 
	
		
			
				|  |  | -City = Boston
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -[Parent]
 | 
	
		
			
				|  |  | -Name = Unknwon
 | 
	
		
			
				|  |  | -Age = 21
 | 
	
		
			
				|  |  | -```
 | 
	
		
			
				|  |  | +Please add `-u` flag to update in the future.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  ## Getting Help
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
 | 
	
		
			
				|  |  |  - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
 | 
	
		
			
				|  |  | -- [File An Issue](https://github.com/go-ini/ini/issues/new)
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -## FAQs
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### What does `BlockMode` field do?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -### Why another INI library?
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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.
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -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)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  ## License
 | 
	
		
			
				|  |  |  
 |