591 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
			
		
		
	
	
			591 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Markdown
		
	
	
ini [](https://drone.io/github.com/go-ini/ini/latest) [](http://gocover.io/github.com/go-ini/ini)
 | 
						|
===
 | 
						|
 | 
						|

 | 
						|
 | 
						|
Package ini provides INI file read and write functionality in Go.
 | 
						|
 | 
						|
[简体中文](README_ZH.md)
 | 
						|
 | 
						|
## Feature
 | 
						|
 | 
						|
- Load multiple data sources(`[]byte` or file) with overwrites.
 | 
						|
- Read with recursion values.
 | 
						|
- Read with parent-child sections.
 | 
						|
- Read with auto-increment key names.
 | 
						|
- Read with multiple-line values.
 | 
						|
- Read with tons of helper methods.
 | 
						|
- Read and convert values to Go types.
 | 
						|
- Read and **WRITE** comments of sections and keys.
 | 
						|
- Manipulate sections, keys and comments with ease.
 | 
						|
- Keep sections and keys in order as you parse and save.
 | 
						|
 | 
						|
## Installation
 | 
						|
 | 
						|
To use a tagged revision:
 | 
						|
 | 
						|
	go get gopkg.in/ini.v1
 | 
						|
 | 
						|
To use with latest changes:
 | 
						|
 | 
						|
	go get github.com/go-ini/ini
 | 
						|
 | 
						|
### Testing
 | 
						|
 | 
						|
If you want to test on your machine, please apply `-t` flag:
 | 
						|
 | 
						|
	go get -t gopkg.in/ini.v1
 | 
						|
 | 
						|
## Getting Started
 | 
						|
 | 
						|
### Loading from data sources
 | 
						|
 | 
						|
A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many as** data sources you want. Passing other types will simply return an error.
 | 
						|
 | 
						|
```go
 | 
						|
cfg, err := ini.Load([]byte("raw data"), "filename")
 | 
						|
```
 | 
						|
 | 
						|
Or start with an empty object:
 | 
						|
 | 
						|
```go
 | 
						|
cfg := ini.Empty()
 | 
						|
```
 | 
						|
 | 
						|
When you cannot decide how many data sources to load at the beginning, you still able to **Append()** them later.
 | 
						|
 | 
						|
```go
 | 
						|
err := cfg.Append("other file", []byte("other raw data"))
 | 
						|
```
 | 
						|
 | 
						|
### 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("")
 | 
						|
```
 | 
						|
 | 
						|
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.GetSection("").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
 | 
						|
```
 | 
						|
 | 
						|
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
 | 
						|
```
 | 
						|
 | 
						|
To auto-split value into slice:
 | 
						|
 | 
						|
```go
 | 
						|
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(",")
 | 
						|
```
 | 
						|
 | 
						|
### 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")
 | 
						|
```
 | 
						|
 | 
						|
## 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
 | 
						|
```
 | 
						|
 | 
						|
### 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
 | 
						|
	None   []int
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
None =
 | 
						|
```
 | 
						|
 | 
						|
#### 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)
 | 
						|
	// ...
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
 | 
						|
 | 
						|
#### 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"`
 | 
						|
}
 | 
						|
 | 
						|
type Config struct {
 | 
						|
	City string
 | 
						|
	Parent
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
Example configuration:
 | 
						|
 | 
						|
```ini
 | 
						|
City = Boston
 | 
						|
 | 
						|
[Parent]
 | 
						|
Name = Unknwon
 | 
						|
Age = 21
 | 
						|
```
 | 
						|
 | 
						|
## Getting Help
 | 
						|
 | 
						|
- [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
 | 
						|
 | 
						|
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
 |