|
|
- INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/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
-
- 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` or a file name with type `string` and you can load **as many data sources as you want**. Passing other types will simply return an error.
-
- ```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 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.
-
- ### 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.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")
- ```
-
- ## 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"]
- ```
-
- ### 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)
- // ...
- }
- ```
-
- 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"`
- }
-
- 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.
|