ini [![Build Status](https://drone.io/github.com/go-ini/ini/status.png)](https://drone.io/github.com/go-ini/ini/latest) [![](http://gocover.io/_badge/github.com/go-ini/ini)](http://gocover.io/github.com/go-ini/ini) === ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) 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 go get 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 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 }) ``` To get value with types: ```go // For boolean values: // true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On // false when value is: 0, f, F, FALSE, false, False, NO, no, No, 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 `%()s`, where `` is the key name in same section or default section, and `%()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("packag_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.