mirror of
https://github.com/thegeeklab/wp-s3-action.git
synced 2024-11-28 09:20:36 +00:00
561 lines
13 KiB
Markdown
561 lines
13 KiB
Markdown
|
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 `%(<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("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.
|