Sfoglia il codice sorgente

vendor: update gopkg.in/ini.v1

Unknwon 6 anni fa
parent
commit
bcf83ea792

+ 1 - 1
vendor/gopkg.in/ini.v1/LICENSE

@@ -176,7 +176,7 @@ recommend that a file or class name and description of purpose be included on
 the same "printed page" as the copyright notice for easier identification within
 third-party archives.
 
-   Copyright [yyyy] [name of copyright owner]
+   Copyright 2014 Unknwon
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.

+ 4 - 1
vendor/gopkg.in/ini.v1/Makefile

@@ -1,4 +1,4 @@
-.PHONY: build test bench vet
+.PHONY: build test bench vet coverage
 
 build: vet bench
 
@@ -10,3 +10,6 @@ bench:
 
 vet:
 	go vet
+
+coverage:
+	go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out

+ 10 - 706
vendor/gopkg.in/ini.v1/README.md

@@ -1,15 +1,13 @@
-INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
+INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/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)
+## 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
 

+ 0 - 727
vendor/gopkg.in/ini.v1/README_ZH.md

@@ -1,727 +0,0 @@
-本包提供了 Go 语言中读写 INI 文件的功能。
-
-## 功能特性
-
-- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
-- 支持递归读取键值
-- 支持读取父子分区
-- 支持读取自增键名
-- 支持读取多行的键值
-- 支持大量辅助方法
-- 支持在读取时直接转换为 Go 语言类型
-- 支持读取和 **写入** 分区和键的注释
-- 轻松操作分区、键值和注释
-- 在保存文件时分区和键值会保持原有的顺序
-
-## 下载安装
-
-使用一个特定版本:
-
-    go get gopkg.in/ini.v1
-
-使用最新版:
-
-	go get github.com/go-ini/ini
-
-如需更新请添加 `-u` 选项。
-
-### 测试安装
-
-如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
-
-	go get -t gopkg.in/ini.v1
-
-如需更新请添加 `-u` 选项。
-
-## 开始使用
-
-### 从数据源加载
-
-一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
-
-```go
-cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
-```
-
-或者从一个空白的文件开始:
-
-```go
-cfg := ini.Empty()
-```
-
-当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
-
-```go
-err := cfg.Append("other file", []byte("other raw data"))
-```
-
-当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
-
-```go
-cfg, err := ini.LooseLoad("filename", "filename_404")
-```
-
-更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
-
-#### 忽略键名的大小写
-
-有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
-
-```go
-cfg, err := ini.InsensitiveLoad("filename")
-//...
-
-// sec1 和 sec2 指向同一个分区对象
-sec1, err := cfg.GetSection("Section")
-sec2, err := cfg.GetSection("SecTIOn")
-
-// key1 和 key2 指向同一个键对象
-key1, err := cfg.GetKey("Key")
-key2, err := cfg.GetKey("KeY")
-```
-
-#### 类似 MySQL 配置中的布尔值键
-
-MySQL 的配置文件中会出现没有具体值的布尔类型的键:
-
-```ini
-[mysqld]
-...
-skip-host-cache
-skip-name-resolve
-```
-
-默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
-
-```go
-cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
-```
-
-这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
-
-如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
-
-```go
-key, err := sec.NewBooleanKey("skip-host-cache")
-```
-
-#### 关于注释
-
-下述几种情况的内容将被视为注释:
-
-1. 所有以 `#` 或 `;` 开头的行
-2. 所有在 `#` 或 `;` 之后的内容
-3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
-
-如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
-
-### 操作分区(Section)
-
-获取指定分区:
-
-```go
-section, err := cfg.GetSection("section name")
-```
-
-如果您想要获取默认分区,则可以用空字符串代替分区名:
-
-```go
-section, err := cfg.GetSection("")
-```
-
-当您非常确定某个分区是存在的,可以使用以下简便方法:
-
-```go
-section := cfg.Section("section name")
-```
-
-如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
-
-创建一个分区:
-
-```go
-err := cfg.NewSection("new section")
-```
-
-获取所有分区对象或名称:
-
-```go
-sections := cfg.Sections()
-names := cfg.SectionStrings()
-```
-
-### 操作键(Key)
-
-获取某个分区下的键:
-
-```go
-key, err := cfg.Section("").GetKey("key name")
-```
-
-和分区一样,您也可以直接获取键而忽略错误处理:
-
-```go
-key := cfg.Section("").Key("key name")
-```
-
-判断某个键是否存在:
-
-```go
-yes := cfg.Section("").HasKey("key name")
-```
-
-创建一个新的键:
-
-```go
-err := cfg.Section("").NewKey("name", "value")
-```
-
-获取分区下的所有键或键名:
-
-```go
-keys := cfg.Section("").Keys()
-names := cfg.Section("").KeyStrings()
-```
-
-获取分区下的所有键值对的克隆:
-
-```go
-hash := cfg.Section("").KeysHash()
-```
-
-### 操作键值(Value)
-
-获取一个类型为字符串(string)的值:
-
-```go
-val := cfg.Section("").Key("key name").String()
-```
-
-获取值的同时通过自定义函数进行处理验证:
-
-```go
-val := cfg.Section("").Key("key name").Validate(func(in string) string {
-	if len(in) == 0 {
-		return "default"
-	}
-	return in
-})
-```
-
-如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
-
-```go
-val := cfg.Section("").Key("key name").Value()
-```
-
-判断某个原值是否存在:
-
-```go
-yes := cfg.Section("").HasValue("test value")
-```
-
-获取其它类型的值:
-
-```go
-// 布尔值的规则:
-// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
-// false 当值为: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
-
-// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
-// 当键不存在或者转换失败时,则会直接返回该默认值。
-// 但是,MustString 方法必须传递一个默认值。
-
-v = cfg.Seciont("").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
-```
-
-如果我的值有好多行怎么办?
-
-```ini
-[advance]
-ADDRESS = """404 road,
-NotFound, State, 5000
-Earth"""
-```
-
-嗯哼?小 case!
-
-```go
-cfg.Section("advance").Key("ADDRESS").String()
-
-/* --- start ---
-404 road,
-NotFound, State, 5000
-Earth
-------  end  --- */
-```
-
-赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
-
-```ini
-[advance]
-two_lines = how about \
-	continuation lines?
-lots_of_lines = 1 \
-	2 \
-	3 \
-	4
-```
-
-简直是小菜一碟!
-
-```go
-cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
-cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
-```
-
-可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{
-	IgnoreContinuation: true,
-}, "filename")
-```
-
-哇靠给力啊!
-
-需要注意的是,值两侧的单引号会被自动剔除:
-
-```ini
-foo = "some value" // foo: some value
-bar = 'some value' // bar: some value
-```
-
-这就是全部了?哈哈,当然不是。
-
-#### 操作键值的辅助方法
-
-获取键值时设定候选值:
-
-```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
-```
-
-如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
-
-验证获取的值是否在指定范围内:
-
-```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
-```
-
-##### 自动分割键值到切片(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 -> [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(",")
-```
-
-从结果切片中剔除无效输入:
-
-```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(",")
-```
-
-当存在无效输入时,直接返回错误:
-
-```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(",")
-```
-
-### 保存配置
-
-终于到了这个时刻,是时候保存一下配置了。
-
-比较原始的做法是输出配置到某个文件:
-
-```go
-// ...
-err = cfg.SaveTo("my.ini")
-err = cfg.SaveToIndent("my.ini", "\t")
-```
-
-另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
-
-```go
-// ...
-cfg.WriteTo(writer)
-cfg.WriteToIndent(writer, "\t")
-```
-
-默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
-
-```go
-ini.PrettyFormat = false
-``` 
-
-## 高级用法
-
-### 递归读取键值
-
-在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
-
-```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
-```
-
-### 读取父子分区
-
-您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
-
-```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
-```
-
-#### 获取上级父分区下的所有键名
-
-```go
-cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
-```
-
-### 无法解析的分区
-
-如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `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  --- */
-```
-
-### 读取自增键名
-
-如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
-
-```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"}
-```
-
-### 映射到结构
-
-想要使用更加面向对象的方式玩转 INI 吗?好主意。
-
-```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)
-	// ...
-
-	// 一切竟可以如此的简单。
-	err = ini.MapTo(p, "path/to/ini")
-	// ...
-
-	// 嗯哼?只需要映射一个分区吗?
-	n := new(Note)
-	err = cfg.Section("Note").MapTo(n)
-	// ...
-}
-```
-
-结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
-
-```go
-// ...
-p := &Person{
-	Name: "Joe",
-}
-// ...
-```
-
-这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
-
-### 从结构反射
-
-可是,我有说不能吗?
-
-```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)
-	// ...
-}
-```
-
-瞧瞧,奇迹发生了。
-
-```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)
-
-为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
-
-目前有 2 款内置的映射器:
-
-- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
-- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
-
-使用方法:
-
-```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)
-	// ...
-}
-```
-
-使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
-
-#### 值映射器(Value Mapper)
-
-值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
-
-```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)
-}
-```
-
-本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
-
-#### 映射/反射的其它说明
-
-任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child
-}
-
-type Config struct {
-	City string
-	Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-
-[Child]
-Age = 21
-```
-
-很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child `ini:"Parent"`
-}
-
-type Config struct {
-	City string
-	Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-Age = 21
-```
-
-## 获取帮助
-
-- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
-- [创建工单](https://github.com/go-ini/ini/issues/new)
-
-## 常见问题
-
-### 字段 `BlockMode` 是什么?
-
-默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
-
-### 为什么要写另一个 INI 解析库?
-
-许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
-
-为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)

+ 407 - 0
vendor/gopkg.in/ini.v1/file.go

@@ -0,0 +1,407 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"strings"
+	"sync"
+)
+
+// File represents a combination of a or more INI file(s) in memory.
+type File struct {
+	options     LoadOptions
+	dataSources []dataSource
+
+	// Should make things safe, but sometimes doesn't matter.
+	BlockMode bool
+	lock      sync.RWMutex
+
+	// To keep data in order.
+	sectionList []string
+	// Actual data is stored here.
+	sections map[string]*Section
+
+	NameMapper
+	ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+	return &File{
+		BlockMode:   true,
+		dataSources: dataSources,
+		sections:    make(map[string]*Section),
+		sectionList: make([]string, 0, 10),
+		options:     opts,
+	}
+}
+
+// Empty returns an empty file object.
+func Empty() *File {
+	// Ignore error here, we sure our data is good.
+	f, _ := Load([]byte(""))
+	return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+	if len(name) == 0 {
+		return nil, errors.New("error creating new section: empty section name")
+	} else if f.options.Insensitive && name != DEFAULT_SECTION {
+		name = strings.ToLower(name)
+	}
+
+	if f.BlockMode {
+		f.lock.Lock()
+		defer f.lock.Unlock()
+	}
+
+	if inSlice(name, f.sectionList) {
+		return f.sections[name], nil
+	}
+
+	f.sectionList = append(f.sectionList, name)
+	f.sections[name] = newSection(f, name)
+	return f.sections[name], nil
+}
+
+// NewRawSection creates a new section with an unparseable body.
+func (f *File) NewRawSection(name, body string) (*Section, error) {
+	section, err := f.NewSection(name)
+	if err != nil {
+		return nil, err
+	}
+
+	section.isRawSection = true
+	section.rawBody = body
+	return section, nil
+}
+
+// NewSections creates a list of sections.
+func (f *File) NewSections(names ...string) (err error) {
+	for _, name := range names {
+		if _, err = f.NewSection(name); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+	if len(name) == 0 {
+		name = DEFAULT_SECTION
+	}
+	if f.options.Insensitive {
+		name = strings.ToLower(name)
+	}
+
+	if f.BlockMode {
+		f.lock.RLock()
+		defer f.lock.RUnlock()
+	}
+
+	sec := f.sections[name]
+	if sec == nil {
+		return nil, fmt.Errorf("section '%s' does not exist", name)
+	}
+	return sec, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+	sec, err := f.GetSection(name)
+	if err != nil {
+		// Note: It's OK here because the only possible error is empty section name,
+		// but if it's empty, this piece of code won't be executed.
+		sec, _ = f.NewSection(name)
+		return sec
+	}
+	return sec
+}
+
+// Section returns list of Section.
+func (f *File) Sections() []*Section {
+	if f.BlockMode {
+		f.lock.RLock()
+		defer f.lock.RUnlock()
+	}
+
+	sections := make([]*Section, len(f.sectionList))
+	for i, name := range f.sectionList {
+		sections[i] = f.sections[name]
+	}
+	return sections
+}
+
+// ChildSections returns a list of child sections of given section name.
+func (f *File) ChildSections(name string) []*Section {
+	return f.Section(name).ChildSections()
+}
+
+// SectionStrings returns list of section names.
+func (f *File) SectionStrings() []string {
+	list := make([]string, len(f.sectionList))
+	copy(list, f.sectionList)
+	return list
+}
+
+// DeleteSection deletes a section.
+func (f *File) DeleteSection(name string) {
+	if f.BlockMode {
+		f.lock.Lock()
+		defer f.lock.Unlock()
+	}
+
+	if len(name) == 0 {
+		name = DEFAULT_SECTION
+	}
+
+	for i, s := range f.sectionList {
+		if s == name {
+			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+			delete(f.sections, name)
+			return
+		}
+	}
+}
+
+func (f *File) reload(s dataSource) error {
+	r, err := s.ReadCloser()
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+	for _, s := range f.dataSources {
+		if err = f.reload(s); err != nil {
+			// In loose mode, we create an empty default section for nonexistent files.
+			if os.IsNotExist(err) && f.options.Loose {
+				f.parse(bytes.NewBuffer(nil))
+				continue
+			}
+			return err
+		}
+	}
+	return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+	ds, err := parseDataSource(source)
+	if err != nil {
+		return err
+	}
+	f.dataSources = append(f.dataSources, ds)
+	for _, s := range others {
+		ds, err = parseDataSource(s)
+		if err != nil {
+			return err
+		}
+		f.dataSources = append(f.dataSources, ds)
+	}
+	return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+	equalSign := "="
+	if PrettyFormat || PrettyEqual {
+		equalSign = " = "
+	}
+
+	// Use buffer to make sure target is safe until finish encoding.
+	buf := bytes.NewBuffer(nil)
+	for i, sname := range f.sectionList {
+		sec := f.Section(sname)
+		if len(sec.Comment) > 0 {
+			if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
+				sec.Comment = "; " + sec.Comment
+			} else {
+				sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:])
+			}
+			if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
+				return nil, err
+			}
+		}
+
+		if i > 0 || DefaultHeader {
+			if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+				return nil, err
+			}
+		} else {
+			// Write nothing if default section is empty
+			if len(sec.keyList) == 0 {
+				continue
+			}
+		}
+
+		if sec.isRawSection {
+			if _, err := buf.WriteString(sec.rawBody); err != nil {
+				return nil, err
+			}
+
+			if PrettySection {
+				// Put a line between sections
+				if _, err := buf.WriteString(LineBreak); err != nil {
+					return nil, err
+				}
+			}
+			continue
+		}
+
+		// Count and generate alignment length and buffer spaces using the
+		// longest key. Keys may be modifed if they contain certain characters so
+		// we need to take that into account in our calculation.
+		alignLength := 0
+		if PrettyFormat {
+			for _, kname := range sec.keyList {
+				keyLength := len(kname)
+				// First case will surround key by ` and second by """
+				if strings.ContainsAny(kname, "\"=:") {
+					keyLength += 2
+				} else if strings.Contains(kname, "`") {
+					keyLength += 6
+				}
+
+				if keyLength > alignLength {
+					alignLength = keyLength
+				}
+			}
+		}
+		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+	KEY_LIST:
+		for _, kname := range sec.keyList {
+			key := sec.Key(kname)
+			if len(key.Comment) > 0 {
+				if len(indent) > 0 && sname != DEFAULT_SECTION {
+					buf.WriteString(indent)
+				}
+				if key.Comment[0] != '#' && key.Comment[0] != ';' {
+					key.Comment = "; " + key.Comment
+				} else {
+					key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:])
+				}
+
+				// Support multiline comments
+				key.Comment = strings.Replace(key.Comment, "\n", "\n; ", -1)
+
+				if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
+					return nil, err
+				}
+			}
+
+			if len(indent) > 0 && sname != DEFAULT_SECTION {
+				buf.WriteString(indent)
+			}
+
+			switch {
+			case key.isAutoIncrement:
+				kname = "-"
+			case strings.ContainsAny(kname, "\"=:"):
+				kname = "`" + kname + "`"
+			case strings.Contains(kname, "`"):
+				kname = `"""` + kname + `"""`
+			}
+
+			for _, val := range key.ValueWithShadows() {
+				if _, err := buf.WriteString(kname); err != nil {
+					return nil, err
+				}
+
+				if key.isBooleanType {
+					if kname != sec.keyList[len(sec.keyList)-1] {
+						buf.WriteString(LineBreak)
+					}
+					continue KEY_LIST
+				}
+
+				// Write out alignment spaces before "=" sign
+				if PrettyFormat {
+					buf.Write(alignSpaces[:alignLength-len(kname)])
+				}
+
+				// In case key value contains "\n", "`", "\"", "#" or ";"
+				if strings.ContainsAny(val, "\n`") {
+					val = `"""` + val + `"""`
+				} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+					val = "`" + val + "`"
+				}
+				if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+					return nil, err
+				}
+			}
+
+			for _, val := range key.nestedValues {
+				if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		if PrettySection {
+			// Put a line between sections
+			if _, err := buf.WriteString(LineBreak); err != nil {
+				return nil, err
+			}
+		}
+	}
+
+	return buf, nil
+}
+
+// WriteToIndent writes content into io.Writer with given indention.
+// If PrettyFormat has been set to be true,
+// it will align "=" sign with spaces under each section.
+func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
+	buf, err := f.writeToBuffer(indent)
+	if err != nil {
+		return 0, err
+	}
+	return buf.WriteTo(w)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+	return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+	// Note: Because we are truncating with os.Create,
+	// 	so it's safer to save to a temporary file location and rename afte done.
+	buf, err := f.writeToBuffer(indent)
+	if err != nil {
+		return err
+	}
+
+	return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+	return f.SaveToIndent(filename, "")
+}

+ 35 - 366
vendor/gopkg.in/ini.v1/ini.go

@@ -17,17 +17,12 @@ package ini
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"os"
 	"regexp"
 	"runtime"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
 )
 
 const (
@@ -37,7 +32,7 @@ const (
 
 	// Maximum allowed depth when recursively substituing variable names.
 	_DEPTH_VALUES = 99
-	_VERSION      = "1.24.0"
+	_VERSION      = "1.37.0"
 )
 
 // Version returns current package version literal.
@@ -58,8 +53,14 @@ var (
 	// or reduce all possible spaces for compact format.
 	PrettyFormat = true
 
+	// Place spaces around "=" sign even when PrettyFormat is false
+	PrettyEqual = false
+
 	// Explicitly write DEFAULT section header
 	DefaultHeader = false
+
+	// Indicate whether to put a line between sections
+	PrettySection = true
 )
 
 func init() {
@@ -91,18 +92,6 @@ func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
 	return os.Open(s.name)
 }
 
-type bytesReadCloser struct {
-	reader io.Reader
-}
-
-func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
-	return rc.reader.Read(p)
-}
-
-func (rc *bytesReadCloser) Close() error {
-	return nil
-}
-
 // sourceData represents an object that contains content in memory.
 type sourceData struct {
 	data []byte
@@ -121,38 +110,6 @@ func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
 	return s.reader, nil
 }
 
-// File represents a combination of a or more INI file(s) in memory.
-type File struct {
-	// Should make things safe, but sometimes doesn't matter.
-	BlockMode bool
-	// Make sure data is safe in multiple goroutines.
-	lock sync.RWMutex
-
-	// Allow combination of multiple data sources.
-	dataSources []dataSource
-	// Actual data is stored here.
-	sections map[string]*Section
-
-	// To keep data in order.
-	sectionList []string
-
-	options LoadOptions
-
-	NameMapper
-	ValueMapper
-}
-
-// newFile initializes File object with given data sources.
-func newFile(dataSources []dataSource, opts LoadOptions) *File {
-	return &File{
-		BlockMode:   true,
-		dataSources: dataSources,
-		sections:    make(map[string]*Section),
-		sectionList: make([]string, 0, 10),
-		options:     opts,
-	}
-}
-
 func parseDataSource(source interface{}) (dataSource, error) {
 	switch s := source.(type) {
 	case string:
@@ -173,9 +130,33 @@ type LoadOptions struct {
 	Insensitive bool
 	// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
 	IgnoreContinuation bool
+	// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+	IgnoreInlineComment bool
 	// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
 	// This type of keys are mostly used in my.cnf.
 	AllowBooleanKeys bool
+	// AllowShadows indicates whether to keep track of keys with same name under same section.
+	AllowShadows bool
+	// AllowNestedValues indicates whether to allow AWS-like nested values.
+	// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+	AllowNestedValues bool
+	// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+	// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+	// Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+	// than the first line of the value.
+	AllowPythonMultilineValues bool
+	// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+	// Docs: https://docs.python.org/2/library/configparser.html
+	// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+	// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+	SpaceBeforeInlineComment bool
+	// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+	// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+	UnescapeValueDoubleQuotes bool
+	// UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+	// when value is NOT surrounded by any quotes.
+	// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+	UnescapeValueCommentSymbols bool
 	// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
 	// conform to key/value pairs. Specify the names of those blocks here.
 	UnparseableSections []string
@@ -219,320 +200,8 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
 	return LoadSources(LoadOptions{Insensitive: true}, source, others...)
 }
 
-// Empty returns an empty file object.
-func Empty() *File {
-	// Ignore error here, we sure our data is good.
-	f, _ := Load([]byte(""))
-	return f
-}
-
-// NewSection creates a new section.
-func (f *File) NewSection(name string) (*Section, error) {
-	if len(name) == 0 {
-		return nil, errors.New("error creating new section: empty section name")
-	} else if f.options.Insensitive && name != DEFAULT_SECTION {
-		name = strings.ToLower(name)
-	}
-
-	if f.BlockMode {
-		f.lock.Lock()
-		defer f.lock.Unlock()
-	}
-
-	if inSlice(name, f.sectionList) {
-		return f.sections[name], nil
-	}
-
-	f.sectionList = append(f.sectionList, name)
-	f.sections[name] = newSection(f, name)
-	return f.sections[name], nil
-}
-
-// NewRawSection creates a new section with an unparseable body.
-func (f *File) NewRawSection(name, body string) (*Section, error) {
-	section, err := f.NewSection(name)
-	if err != nil {
-		return nil, err
-	}
-
-	section.isRawSection = true
-	section.rawBody = body
-	return section, nil
-}
-
-// NewSections creates a list of sections.
-func (f *File) NewSections(names ...string) (err error) {
-	for _, name := range names {
-		if _, err = f.NewSection(name); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-// GetSection returns section by given name.
-func (f *File) GetSection(name string) (*Section, error) {
-	if len(name) == 0 {
-		name = DEFAULT_SECTION
-	} else if f.options.Insensitive {
-		name = strings.ToLower(name)
-	}
-
-	if f.BlockMode {
-		f.lock.RLock()
-		defer f.lock.RUnlock()
-	}
-
-	sec := f.sections[name]
-	if sec == nil {
-		return nil, fmt.Errorf("section '%s' does not exist", name)
-	}
-	return sec, nil
-}
-
-// Section assumes named section exists and returns a zero-value when not.
-func (f *File) Section(name string) *Section {
-	sec, err := f.GetSection(name)
-	if err != nil {
-		// Note: It's OK here because the only possible error is empty section name,
-		// but if it's empty, this piece of code won't be executed.
-		sec, _ = f.NewSection(name)
-		return sec
-	}
-	return sec
-}
-
-// Section returns list of Section.
-func (f *File) Sections() []*Section {
-	sections := make([]*Section, len(f.sectionList))
-	for i := range f.sectionList {
-		sections[i] = f.Section(f.sectionList[i])
-	}
-	return sections
-}
-
-// SectionStrings returns list of section names.
-func (f *File) SectionStrings() []string {
-	list := make([]string, len(f.sectionList))
-	copy(list, f.sectionList)
-	return list
-}
-
-// DeleteSection deletes a section.
-func (f *File) DeleteSection(name string) {
-	if f.BlockMode {
-		f.lock.Lock()
-		defer f.lock.Unlock()
-	}
-
-	if len(name) == 0 {
-		name = DEFAULT_SECTION
-	}
-
-	for i, s := range f.sectionList {
-		if s == name {
-			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
-			delete(f.sections, name)
-			return
-		}
-	}
-}
-
-func (f *File) reload(s dataSource) error {
-	r, err := s.ReadCloser()
-	if err != nil {
-		return err
-	}
-	defer r.Close()
-
-	return f.parse(r)
-}
-
-// Reload reloads and parses all data sources.
-func (f *File) Reload() (err error) {
-	for _, s := range f.dataSources {
-		if err = f.reload(s); err != nil {
-			// In loose mode, we create an empty default section for nonexistent files.
-			if os.IsNotExist(err) && f.options.Loose {
-				f.parse(bytes.NewBuffer(nil))
-				continue
-			}
-			return err
-		}
-	}
-	return nil
-}
-
-// Append appends one or more data sources and reloads automatically.
-func (f *File) Append(source interface{}, others ...interface{}) error {
-	ds, err := parseDataSource(source)
-	if err != nil {
-		return err
-	}
-	f.dataSources = append(f.dataSources, ds)
-	for _, s := range others {
-		ds, err = parseDataSource(s)
-		if err != nil {
-			return err
-		}
-		f.dataSources = append(f.dataSources, ds)
-	}
-	return f.Reload()
-}
-
-// WriteToIndent writes content into io.Writer with given indention.
-// If PrettyFormat has been set to be true,
-// it will align "=" sign with spaces under each section.
-func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
-	equalSign := "="
-	if PrettyFormat {
-		equalSign = " = "
-	}
-
-	// Use buffer to make sure target is safe until finish encoding.
-	buf := bytes.NewBuffer(nil)
-	for i, sname := range f.sectionList {
-		sec := f.Section(sname)
-		if len(sec.Comment) > 0 {
-			if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
-				sec.Comment = "; " + sec.Comment
-			}
-			if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
-				return 0, err
-			}
-		}
-
-		if i > 0 || DefaultHeader {
-			if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
-				return 0, err
-			}
-		} else {
-			// Write nothing if default section is empty
-			if len(sec.keyList) == 0 {
-				continue
-			}
-		}
-
-		if sec.isRawSection {
-			if _, err = buf.WriteString(sec.rawBody); err != nil {
-				return 0, err
-			}
-			continue
-		}
-
-		// Count and generate alignment length and buffer spaces using the
-		// longest key. Keys may be modifed if they contain certain characters so
-		// we need to take that into account in our calculation.
-		alignLength := 0
-		if PrettyFormat {
-			for _, kname := range sec.keyList {
-				keyLength := len(kname)
-				// First case will surround key by ` and second by """
-				if strings.ContainsAny(kname, "\"=:") {
-					keyLength += 2
-				} else if strings.Contains(kname, "`") {
-					keyLength += 6
-				}
-
-				if keyLength > alignLength {
-					alignLength = keyLength
-				}
-			}
-		}
-		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
-
-		for _, kname := range sec.keyList {
-			key := sec.Key(kname)
-			if len(key.Comment) > 0 {
-				if len(indent) > 0 && sname != DEFAULT_SECTION {
-					buf.WriteString(indent)
-				}
-				if key.Comment[0] != '#' && key.Comment[0] != ';' {
-					key.Comment = "; " + key.Comment
-				}
-				if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
-					return 0, err
-				}
-			}
-
-			if len(indent) > 0 && sname != DEFAULT_SECTION {
-				buf.WriteString(indent)
-			}
-
-			switch {
-			case key.isAutoIncrement:
-				kname = "-"
-			case strings.ContainsAny(kname, "\"=:"):
-				kname = "`" + kname + "`"
-			case strings.Contains(kname, "`"):
-				kname = `"""` + kname + `"""`
-			}
-			if _, err = buf.WriteString(kname); err != nil {
-				return 0, err
-			}
-
-			if key.isBooleanType {
-				if kname != sec.keyList[len(sec.keyList)-1] {
-					buf.WriteString(LineBreak)
-				}
-				continue
-			}
-
-			// Write out alignment spaces before "=" sign
-			if PrettyFormat {
-				buf.Write(alignSpaces[:alignLength-len(kname)])
-			}
-
-			val := key.value
-			// In case key value contains "\n", "`", "\"", "#" or ";"
-			if strings.ContainsAny(val, "\n`") {
-				val = `"""` + val + `"""`
-			} else if strings.ContainsAny(val, "#;") {
-				val = "`" + val + "`"
-			}
-			if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
-				return 0, err
-			}
-		}
-
-		// Put a line between sections
-		if _, err = buf.WriteString(LineBreak); err != nil {
-			return 0, err
-		}
-	}
-
-	return buf.WriteTo(w)
-}
-
-// WriteTo writes file content into io.Writer.
-func (f *File) WriteTo(w io.Writer) (int64, error) {
-	return f.WriteToIndent(w, "")
-}
-
-// SaveToIndent writes content to file system with given value indention.
-func (f *File) SaveToIndent(filename, indent string) error {
-	// Note: Because we are truncating with os.Create,
-	// 	so it's safer to save to a temporary file location and rename afte done.
-	tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
-	defer os.Remove(tmpPath)
-
-	fw, err := os.Create(tmpPath)
-	if err != nil {
-		return err
-	}
-
-	if _, err = f.WriteToIndent(fw, indent); err != nil {
-		fw.Close()
-		return err
-	}
-	fw.Close()
-
-	// Remove old file and rename the new one.
-	os.Remove(filename)
-	return os.Rename(tmpPath, filename)
-}
-
-// SaveTo writes content to file system.
-func (f *File) SaveTo(filename string) error {
-	return f.SaveToIndent(filename, "")
+// InsensitiveLoad has exactly same functionality as Load function
+// except it allows have shadow keys.
+func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
 }

+ 164 - 46
vendor/gopkg.in/ini.v1/key.go

@@ -15,6 +15,8 @@
 package ini
 
 import (
+	"bytes"
+	"errors"
 	"fmt"
 	"strconv"
 	"strings"
@@ -24,12 +26,62 @@ import (
 // Key represents a key under a section.
 type Key struct {
 	s               *Section
+	Comment         string
 	name            string
 	value           string
 	isAutoIncrement bool
 	isBooleanType   bool
 
-	Comment string
+	isShadow bool
+	shadows  []*Key
+
+	nestedValues []string
+}
+
+// newKey simply return a key object with given values.
+func newKey(s *Section, name, val string) *Key {
+	return &Key{
+		s:     s,
+		name:  name,
+		value: val,
+	}
+}
+
+func (k *Key) addShadow(val string) error {
+	if k.isShadow {
+		return errors.New("cannot add shadow to another shadow key")
+	} else if k.isAutoIncrement || k.isBooleanType {
+		return errors.New("cannot add shadow to auto-increment or boolean key")
+	}
+
+	shadow := newKey(k.s, k.name, val)
+	shadow.isShadow = true
+	k.shadows = append(k.shadows, shadow)
+	return nil
+}
+
+// AddShadow adds a new shadow key to itself.
+func (k *Key) AddShadow(val string) error {
+	if !k.s.f.options.AllowShadows {
+		return errors.New("shadow key is not allowed")
+	}
+	return k.addShadow(val)
+}
+
+func (k *Key) addNestedValue(val string) error {
+	if k.isAutoIncrement || k.isBooleanType {
+		return errors.New("cannot add nested value to auto-increment or boolean key")
+	}
+
+	k.nestedValues = append(k.nestedValues, val)
+	return nil
+}
+
+func (k *Key) AddNestedValue(val string) error {
+	if !k.s.f.options.AllowNestedValues {
+		return errors.New("nested value is not allowed")
+	}
+	return k.addNestedValue(val)
 }
 
 // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
@@ -45,16 +97,35 @@ func (k *Key) Value() string {
 	return k.value
 }
 
-// String returns string representation of value.
-func (k *Key) String() string {
-	val := k.value
+// ValueWithShadows returns raw values of key and its shadows if any.
+func (k *Key) ValueWithShadows() []string {
+	if len(k.shadows) == 0 {
+		return []string{k.value}
+	}
+	vals := make([]string, len(k.shadows)+1)
+	vals[0] = k.value
+	for i := range k.shadows {
+		vals[i+1] = k.shadows[i].value
+	}
+	return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+	return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
 	if k.s.f.ValueMapper != nil {
 		val = k.s.f.ValueMapper(val)
 	}
-	if strings.Index(val, "%") == -1 {
+
+	// Fail-fast if no indicate char found for recursive value
+	if !strings.Contains(val, "%") {
 		return val
 	}
-
 	for i := 0; i < _DEPTH_VALUES; i++ {
 		vr := varPattern.FindString(val)
 		if len(vr) == 0 {
@@ -67,7 +138,7 @@ func (k *Key) String() string {
 
 		// Search in the same section.
 		nk, err := k.s.GetKey(noption)
-		if err != nil {
+		if err != nil || k == nk {
 			// Search again in default section.
 			nk, _ = k.s.f.Section("").GetKey(noption)
 		}
@@ -78,6 +149,11 @@ func (k *Key) String() string {
 	return val
 }
 
+// String returns string representation of value.
+func (k *Key) String() string {
+	return k.transformValue(k.value)
+}
+
 // Validate accepts a validate function which can
 // return modifed result as key value.
 func (k *Key) Validate(fn func(string) string) string {
@@ -392,47 +468,95 @@ func (k *Key) Strings(delim string) []string {
 		return []string{}
 	}
 
-	vals := strings.Split(str, delim)
-	for i := range vals {
-		vals[i] = strings.TrimSpace(vals[i])
+	runes := []rune(str)
+	vals := make([]string, 0, 2)
+	var buf bytes.Buffer
+	escape := false
+	idx := 0
+	for {
+		if escape {
+			escape = false
+			if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+				buf.WriteRune('\\')
+			}
+			buf.WriteRune(runes[idx])
+		} else {
+			if runes[idx] == '\\' {
+				escape = true
+			} else if strings.HasPrefix(string(runes[idx:]), delim) {
+				idx += len(delim) - 1
+				vals = append(vals, strings.TrimSpace(buf.String()))
+				buf.Reset()
+			} else {
+				buf.WriteRune(runes[idx])
+			}
+		}
+		idx += 1
+		if idx == len(runes) {
+			break
+		}
+	}
+
+	if buf.Len() > 0 {
+		vals = append(vals, strings.TrimSpace(buf.String()))
 	}
+
 	return vals
 }
 
+// StringsWithShadows returns list of string divided by given delimiter.
+// Shadows will also be appended if any.
+func (k *Key) StringsWithShadows(delim string) []string {
+	vals := k.ValueWithShadows()
+	results := make([]string, 0, len(vals)*2)
+	for i := range vals {
+		if len(vals) == 0 {
+			continue
+		}
+
+		results = append(results, strings.Split(vals[i], delim)...)
+	}
+
+	for i := range results {
+		results[i] = k.transformValue(strings.TrimSpace(results[i]))
+	}
+	return results
+}
+
 // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
 func (k *Key) Float64s(delim string) []float64 {
-	vals, _ := k.getFloat64s(delim, true, false)
+	vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
 	return vals
 }
 
 // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
 func (k *Key) Ints(delim string) []int {
-	vals, _ := k.getInts(delim, true, false)
+	vals, _ := k.parseInts(k.Strings(delim), true, false)
 	return vals
 }
 
 // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
 func (k *Key) Int64s(delim string) []int64 {
-	vals, _ := k.getInt64s(delim, true, false)
+	vals, _ := k.parseInt64s(k.Strings(delim), true, false)
 	return vals
 }
 
 // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
 func (k *Key) Uints(delim string) []uint {
-	vals, _ := k.getUints(delim, true, false)
+	vals, _ := k.parseUints(k.Strings(delim), true, false)
 	return vals
 }
 
 // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
 func (k *Key) Uint64s(delim string) []uint64 {
-	vals, _ := k.getUint64s(delim, true, false)
+	vals, _ := k.parseUint64s(k.Strings(delim), true, false)
 	return vals
 }
 
 // TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
 // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
 func (k *Key) TimesFormat(format, delim string) []time.Time {
-	vals, _ := k.getTimesFormat(format, delim, true, false)
+	vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
 	return vals
 }
 
@@ -445,41 +569,41 @@ func (k *Key) Times(delim string) []time.Time {
 // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
 // it will not be included to result list.
 func (k *Key) ValidFloat64s(delim string) []float64 {
-	vals, _ := k.getFloat64s(delim, false, false)
+	vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
 	return vals
 }
 
 // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
 // not be included to result list.
 func (k *Key) ValidInts(delim string) []int {
-	vals, _ := k.getInts(delim, false, false)
+	vals, _ := k.parseInts(k.Strings(delim), false, false)
 	return vals
 }
 
 // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
 // then it will not be included to result list.
 func (k *Key) ValidInt64s(delim string) []int64 {
-	vals, _ := k.getInt64s(delim, false, false)
+	vals, _ := k.parseInt64s(k.Strings(delim), false, false)
 	return vals
 }
 
 // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
 // then it will not be included to result list.
 func (k *Key) ValidUints(delim string) []uint {
-	vals, _ := k.getUints(delim, false, false)
+	vals, _ := k.parseUints(k.Strings(delim), false, false)
 	return vals
 }
 
 // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
 // integer, then it will not be included to result list.
 func (k *Key) ValidUint64s(delim string) []uint64 {
-	vals, _ := k.getUint64s(delim, false, false)
+	vals, _ := k.parseUint64s(k.Strings(delim), false, false)
 	return vals
 }
 
 // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
 func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
-	vals, _ := k.getTimesFormat(format, delim, false, false)
+	vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
 	return vals
 }
 
@@ -490,33 +614,33 @@ func (k *Key) ValidTimes(delim string) []time.Time {
 
 // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
 func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
-	return k.getFloat64s(delim, false, true)
+	return k.parseFloat64s(k.Strings(delim), false, true)
 }
 
 // StrictInts returns list of int divided by given delimiter or error on first invalid input.
 func (k *Key) StrictInts(delim string) ([]int, error) {
-	return k.getInts(delim, false, true)
+	return k.parseInts(k.Strings(delim), false, true)
 }
 
 // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
 func (k *Key) StrictInt64s(delim string) ([]int64, error) {
-	return k.getInt64s(delim, false, true)
+	return k.parseInt64s(k.Strings(delim), false, true)
 }
 
 // StrictUints returns list of uint divided by given delimiter or error on first invalid input.
 func (k *Key) StrictUints(delim string) ([]uint, error) {
-	return k.getUints(delim, false, true)
+	return k.parseUints(k.Strings(delim), false, true)
 }
 
 // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
 func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
-	return k.getUint64s(delim, false, true)
+	return k.parseUint64s(k.Strings(delim), false, true)
 }
 
 // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
 // or error on first invalid input.
 func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
-	return k.getTimesFormat(format, delim, false, true)
+	return k.parseTimesFormat(format, k.Strings(delim), false, true)
 }
 
 // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
@@ -525,9 +649,8 @@ func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
 	return k.StrictTimesFormat(time.RFC3339, delim)
 }
 
-// getFloat64s returns list of float64 divided by given delimiter.
-func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]float64, error) {
-	strs := k.Strings(delim)
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
 	vals := make([]float64, 0, len(strs))
 	for _, str := range strs {
 		val, err := strconv.ParseFloat(str, 64)
@@ -541,9 +664,8 @@ func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]flo
 	return vals, nil
 }
 
-// getInts returns list of int divided by given delimiter.
-func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, error) {
-	strs := k.Strings(delim)
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
 	vals := make([]int, 0, len(strs))
 	for _, str := range strs {
 		val, err := strconv.Atoi(str)
@@ -557,9 +679,8 @@ func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, er
 	return vals, nil
 }
 
-// getInt64s returns list of int64 divided by given delimiter.
-func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64, error) {
-	strs := k.Strings(delim)
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
 	vals := make([]int64, 0, len(strs))
 	for _, str := range strs {
 		val, err := strconv.ParseInt(str, 10, 64)
@@ -573,9 +694,8 @@ func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64
 	return vals, nil
 }
 
-// getUints returns list of uint divided by given delimiter.
-func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint, error) {
-	strs := k.Strings(delim)
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
 	vals := make([]uint, 0, len(strs))
 	for _, str := range strs {
 		val, err := strconv.ParseUint(str, 10, 0)
@@ -589,9 +709,8 @@ func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint,
 	return vals, nil
 }
 
-// getUint64s returns list of uint64 divided by given delimiter.
-func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
-	strs := k.Strings(delim)
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
 	vals := make([]uint64, 0, len(strs))
 	for _, str := range strs {
 		val, err := strconv.ParseUint(str, 10, 64)
@@ -605,9 +724,8 @@ func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint
 	return vals, nil
 }
 
-// getTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
-func (k *Key) getTimesFormat(format, delim string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
-	strs := k.Strings(delim)
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
 	vals := make([]time.Time, 0, len(strs))
 	for _, str := range strs {
 		val, err := time.Parse(format, str)

+ 147 - 16
vendor/gopkg.in/ini.v1/parser.go

@@ -19,11 +19,14 @@ import (
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 	"strings"
 	"unicode"
 )
 
+var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
+
 type tokenType int
 
 const (
@@ -189,11 +192,14 @@ func (p *parser) readContinuationLines(val string) (string, error) {
 // are quotes \" or \'.
 // It returns false if any other parts also contain same kind of quotes.
 func hasSurroundedQuote(in string, quote byte) bool {
-	return len(in) > 2 && in[0] == quote && in[len(in)-1] == quote &&
+	return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
 		strings.IndexByte(in[1:], quote) == len(in)-2
 }
 
-func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
+func (p *parser) readValue(in []byte,
+	parserBufferSize int,
+	ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment bool) (string, error) {
+
 	line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
 	if len(line) == 0 {
 		return "", nil
@@ -204,6 +210,8 @@ func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
 		valQuote = `"""`
 	} else if line[0] == '`' {
 		valQuote = "`"
+	} else if unescapeValueDoubleQuotes && line[0] == '"' {
+		valQuote = `"`
 	}
 
 	if len(valQuote) > 0 {
@@ -214,28 +222,97 @@ func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
 			return p.readMultilines(line, line[startIdx:], valQuote)
 		}
 
+		if unescapeValueDoubleQuotes && valQuote == `"` {
+			return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+		}
 		return line[startIdx : pos+startIdx], nil
 	}
 
-	// Won't be able to reach here if value only contains whitespace.
+	lastChar := line[len(line)-1]
+	// Won't be able to reach here if value only contains whitespace
 	line = strings.TrimSpace(line)
+	trimmedLastChar := line[len(line)-1]
 
-	// Check continuation lines when desired.
-	if !ignoreContinuation && line[len(line)-1] == '\\' {
+	// Check continuation lines when desired
+	if !ignoreContinuation && trimmedLastChar == '\\' {
 		return p.readContinuationLines(line[:len(line)-1])
 	}
 
-	i := strings.IndexAny(line, "#;")
-	if i > -1 {
-		p.comment.WriteString(line[i:])
-		line = strings.TrimSpace(line[:i])
+	// Check if ignore inline comment
+	if !ignoreInlineComment {
+		var i int
+		if spaceBeforeInlineComment {
+			i = strings.Index(line, " #")
+			if i == -1 {
+				i = strings.Index(line, " ;")
+			}
+
+		} else {
+			i = strings.IndexAny(line, "#;")
+		}
+
+		if i > -1 {
+			p.comment.WriteString(line[i:])
+			line = strings.TrimSpace(line[:i])
+		}
+
 	}
 
-	// Trim single quotes
+	// Trim single and double quotes
 	if hasSurroundedQuote(line, '\'') ||
 		hasSurroundedQuote(line, '"') {
 		line = line[1 : len(line)-1]
+	} else if len(valQuote) == 0 && unescapeValueCommentSymbols {
+		if strings.Contains(line, `\;`) {
+			line = strings.Replace(line, `\;`, ";", -1)
+		}
+		if strings.Contains(line, `\#`) {
+			line = strings.Replace(line, `\#`, "#", -1)
+		}
+	} else if allowPythonMultilines && lastChar == '\n' {
+		parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
+		peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+		identSize := -1
+		val := line
+
+		for {
+			peekData, peekErr := peekBuffer.ReadBytes('\n')
+			if peekErr != nil {
+				if peekErr == io.EOF {
+					return val, nil
+				}
+				return "", peekErr
+			}
+
+			peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+			if len(peekMatches) != 3 {
+				return val, nil
+			}
+
+			currentIdentSize := len(peekMatches[1])
+			// NOTE: Return if not a python-ini multi-line value.
+			if currentIdentSize < 0 {
+				return val, nil
+			}
+			identSize = currentIdentSize
+
+			// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
+			_, err := p.readUntil('\n')
+			if err != nil {
+				return "", err
+			}
+
+			val += fmt.Sprintf("\n%s", peekMatches[2])
+		}
+
+		// NOTE: If it was a Python multi-line value,
+		// return the appended value.
+		if identSize > 0 {
+			return val, nil
+		}
 	}
+
 	return line, nil
 }
 
@@ -247,16 +324,55 @@ func (f *File) parse(reader io.Reader) (err error) {
 	}
 
 	// Ignore error because default section name is never empty string.
-	section, _ := f.NewSection(DEFAULT_SECTION)
+	name := DEFAULT_SECTION
+	if f.options.Insensitive {
+		name = strings.ToLower(DEFAULT_SECTION)
+	}
+	section, _ := f.NewSection(name)
+
+	// This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+	var isLastValueEmpty bool
+	var lastRegularKey *Key
 
 	var line []byte
 	var inUnparseableSection bool
+
+	// NOTE: Iterate and increase `currentPeekSize` until
+	// the size of the parser buffer is found.
+	// TODO: When Golang 1.10 is the lowest version supported,
+	// replace with `parserBufferSize := p.buf.Size()`.
+	parserBufferSize := 0
+	// NOTE: Peek 1kb at a time.
+	currentPeekSize := 1024
+
+	if f.options.AllowPythonMultilineValues {
+		for {
+			peekBytes, _ := p.buf.Peek(currentPeekSize)
+			peekBytesLength := len(peekBytes)
+
+			if parserBufferSize >= peekBytesLength {
+				break
+			}
+
+			currentPeekSize *= 2
+			parserBufferSize = peekBytesLength
+		}
+	}
+
 	for !p.isEOF {
 		line, err = p.readUntil('\n')
 		if err != nil {
 			return err
 		}
 
+		if f.options.AllowNestedValues &&
+			isLastValueEmpty && len(line) > 0 {
+			if line[0] == ' ' || line[0] == '\t' {
+				lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+				continue
+			}
+		}
+
 		line = bytes.TrimLeftFunc(line, unicode.IsSpace)
 		if len(line) == 0 {
 			continue
@@ -318,7 +434,14 @@ func (f *File) parse(reader io.Reader) (err error) {
 		if err != nil {
 			// Treat as boolean key when desired, and whole line is key name.
 			if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
-				kname, err := p.readValue(line, f.options.IgnoreContinuation)
+				kname, err := p.readValue(line,
+					parserBufferSize,
+					f.options.IgnoreContinuation,
+					f.options.IgnoreInlineComment,
+					f.options.UnescapeValueDoubleQuotes,
+					f.options.UnescapeValueCommentSymbols,
+					f.options.AllowPythonMultilineValues,
+					f.options.SpaceBeforeInlineComment)
 				if err != nil {
 					return err
 				}
@@ -341,19 +464,27 @@ func (f *File) parse(reader io.Reader) (err error) {
 			p.count++
 		}
 
-		key, err := section.NewKey(kname, "")
+		value, err := p.readValue(line[offset:],
+			parserBufferSize,
+			f.options.IgnoreContinuation,
+			f.options.IgnoreInlineComment,
+			f.options.UnescapeValueDoubleQuotes,
+			f.options.UnescapeValueCommentSymbols,
+			f.options.AllowPythonMultilineValues,
+			f.options.SpaceBeforeInlineComment)
 		if err != nil {
 			return err
 		}
-		key.isAutoIncrement = isAutoIncr
+		isLastValueEmpty = len(value) == 0
 
-		value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
+		key, err := section.NewKey(kname, value)
 		if err != nil {
 			return err
 		}
-		key.SetValue(value)
+		key.isAutoIncrement = isAutoIncr
 		key.Comment = strings.TrimSpace(p.comment.String())
 		p.comment.Reset()
+		lastRegularKey = key
 	}
 	return nil
 }

+ 31 - 6
vendor/gopkg.in/ini.v1/section.go

@@ -54,6 +54,14 @@ func (s *Section) Body() string {
 	return strings.TrimSpace(s.rawBody)
 }
 
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+	if !s.isRawSection {
+		return
+	}
+	s.rawBody = body
+}
+
 // NewKey creates a new key to given section.
 func (s *Section) NewKey(name, val string) (*Key, error) {
 	if len(name) == 0 {
@@ -68,16 +76,18 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
 	}
 
 	if inSlice(name, s.keyList) {
-		s.keys[name].value = val
+		if s.f.options.AllowShadows {
+			if err := s.keys[name].addShadow(val); err != nil {
+				return nil, err
+			}
+		} else {
+			s.keys[name].value = val
+		}
 		return s.keys[name], nil
 	}
 
 	s.keyList = append(s.keyList, name)
-	s.keys[name] = &Key{
-		s:     s,
-		name:  name,
-		value: val,
-	}
+	s.keys[name] = newKey(s, name, val)
 	s.keysHash[name] = val
 	return s.keys[name], nil
 }
@@ -134,6 +144,7 @@ func (s *Section) HasKey(name string) bool {
 }
 
 // Haskey is a backwards-compatible name for HasKey.
+// TODO: delete me in v2
 func (s *Section) Haskey(name string) bool {
 	return s.HasKey(name)
 }
@@ -230,3 +241,17 @@ func (s *Section) DeleteKey(name string) {
 		}
 	}
 }
+
+// ChildSections returns a list of child sections of current section.
+// For example, "[parent.child1]" and "[parent.child12]" are child sections
+// of section "[parent]".
+func (s *Section) ChildSections() []*Section {
+	prefix := s.name + "."
+	children := make([]*Section, 0, 3)
+	for _, name := range s.f.sectionList {
+		if strings.HasPrefix(name, prefix) {
+			children = append(children, s.f.sections[name])
+		}
+	}
+	return children
+}

+ 106 - 25
vendor/gopkg.in/ini.v1/struct.go

@@ -78,34 +78,44 @@ func parseDelim(actual string) string {
 var reflectTime = reflect.TypeOf(time.Now()).Kind()
 
 // setSliceWithProperType sets proper values to slice based on its type.
-func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
-	strs := key.Strings(delim)
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+	var strs []string
+	if allowShadow {
+		strs = key.StringsWithShadows(delim)
+	} else {
+		strs = key.Strings(delim)
+	}
+
 	numVals := len(strs)
 	if numVals == 0 {
 		return nil
 	}
 
 	var vals interface{}
+	var err error
 
 	sliceOf := field.Type().Elem().Kind()
 	switch sliceOf {
 	case reflect.String:
 		vals = strs
 	case reflect.Int:
-		vals = key.Ints(delim)
+		vals, err = key.parseInts(strs, true, false)
 	case reflect.Int64:
-		vals = key.Int64s(delim)
+		vals, err = key.parseInt64s(strs, true, false)
 	case reflect.Uint:
-		vals = key.Uints(delim)
+		vals, err = key.parseUints(strs, true, false)
 	case reflect.Uint64:
-		vals = key.Uint64s(delim)
+		vals, err = key.parseUint64s(strs, true, false)
 	case reflect.Float64:
-		vals = key.Float64s(delim)
+		vals, err = key.parseFloat64s(strs, true, false)
 	case reflectTime:
-		vals = key.Times(delim)
+		vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
 	default:
 		return fmt.Errorf("unsupported type '[]%s'", sliceOf)
 	}
+	if err != nil && isStrict {
+		return err
+	}
 
 	slice := reflect.MakeSlice(field.Type(), numVals, numVals)
 	for i := 0; i < numVals; i++ {
@@ -130,10 +140,17 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
 	return nil
 }
 
+func wrapStrictError(err error, isStrict bool) error {
+	if isStrict {
+		return err
+	}
+	return nil
+}
+
 // setWithProperType sets proper value to field based on its type,
 // but it does not return error for failing parsing,
 // because we want to use default value that is already assigned to strcut.
-func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
 	switch t.Kind() {
 	case reflect.String:
 		if len(key.String()) == 0 {
@@ -143,20 +160,20 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
 	case reflect.Bool:
 		boolVal, err := key.Bool()
 		if err != nil {
-			return nil
+			return wrapStrictError(err, isStrict)
 		}
 		field.SetBool(boolVal)
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		durationVal, err := key.Duration()
 		// Skip zero value
-		if err == nil && int(durationVal) > 0 {
+		if err == nil && int64(durationVal) > 0 {
 			field.Set(reflect.ValueOf(durationVal))
 			return nil
 		}
 
 		intVal, err := key.Int64()
-		if err != nil || intVal == 0 {
-			return nil
+		if err != nil {
+			return wrapStrictError(err, isStrict)
 		}
 		field.SetInt(intVal)
 	//	byte is an alias for uint8, so supporting uint8 breaks support for byte
@@ -170,31 +187,43 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
 
 		uintVal, err := key.Uint64()
 		if err != nil {
-			return nil
+			return wrapStrictError(err, isStrict)
 		}
 		field.SetUint(uintVal)
 
 	case reflect.Float32, reflect.Float64:
 		floatVal, err := key.Float64()
 		if err != nil {
-			return nil
+			return wrapStrictError(err, isStrict)
 		}
 		field.SetFloat(floatVal)
 	case reflectTime:
 		timeVal, err := key.Time()
 		if err != nil {
-			return nil
+			return wrapStrictError(err, isStrict)
 		}
 		field.Set(reflect.ValueOf(timeVal))
 	case reflect.Slice:
-		return setSliceWithProperType(key, field, delim)
+		return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
 	default:
 		return fmt.Errorf("unsupported type '%s'", t)
 	}
 	return nil
 }
 
-func (s *Section) mapTo(val reflect.Value) error {
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
+	opts := strings.SplitN(tag, ",", 3)
+	rawName = opts[0]
+	if len(opts) > 1 {
+		omitEmpty = opts[1] == "omitempty"
+	}
+	if len(opts) > 2 {
+		allowShadow = opts[2] == "allowshadow"
+	}
+	return rawName, omitEmpty, allowShadow
+}
+
+func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
 	if val.Kind() == reflect.Ptr {
 		val = val.Elem()
 	}
@@ -209,8 +238,8 @@ func (s *Section) mapTo(val reflect.Value) error {
 			continue
 		}
 
-		opts := strings.SplitN(tag, ",", 2) // strip off possible omitempty
-		fieldName := s.parseFieldName(tpField.Name, opts[0])
+		rawName, _, allowShadow := parseTagOptions(tag)
+		fieldName := s.parseFieldName(tpField.Name, rawName)
 		if len(fieldName) == 0 || !field.CanSet() {
 			continue
 		}
@@ -223,7 +252,7 @@ func (s *Section) mapTo(val reflect.Value) error {
 
 		if isAnonymous || isStruct {
 			if sec, err := s.f.GetSection(fieldName); err == nil {
-				if err = sec.mapTo(field); err != nil {
+				if err = sec.mapTo(field, isStrict); err != nil {
 					return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
 				}
 				continue
@@ -231,7 +260,8 @@ func (s *Section) mapTo(val reflect.Value) error {
 		}
 
 		if key, err := s.GetKey(fieldName); err == nil {
-			if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
+			delim := parseDelim(tpField.Tag.Get("delim"))
+			if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
 				return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
 			}
 		}
@@ -250,7 +280,22 @@ func (s *Section) MapTo(v interface{}) error {
 		return errors.New("cannot map to non-pointer struct")
 	}
 
-	return s.mapTo(val)
+	return s.mapTo(val, false)
+}
+
+// MapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+	typ := reflect.TypeOf(v)
+	val := reflect.ValueOf(v)
+	if typ.Kind() == reflect.Ptr {
+		typ = typ.Elem()
+		val = val.Elem()
+	} else {
+		return errors.New("cannot map to non-pointer struct")
+	}
+
+	return s.mapTo(val, true)
 }
 
 // MapTo maps file to given struct.
@@ -258,6 +303,12 @@ func (f *File) MapTo(v interface{}) error {
 	return f.Section("").MapTo(v)
 }
 
+// MapTo maps file to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (f *File) StrictMapTo(v interface{}) error {
+	return f.Section("").StrictMapTo(v)
+}
+
 // MapTo maps data sources to given struct with name mapper.
 func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
 	cfg, err := Load(source, others...)
@@ -268,11 +319,28 @@ func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, other
 	return cfg.MapTo(v)
 }
 
+// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+	cfg, err := Load(source, others...)
+	if err != nil {
+		return err
+	}
+	cfg.NameMapper = mapper
+	return cfg.StrictMapTo(v)
+}
+
 // MapTo maps data sources to given struct.
 func MapTo(v, source interface{}, others ...interface{}) error {
 	return MapToWithMapper(v, nil, source, others...)
 }
 
+// StrictMapTo maps data sources to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapTo(v, source interface{}, others ...interface{}) error {
+	return StrictMapToWithMapper(v, nil, source, others...)
+}
+
 // reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
 func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
 	slice := field.Slice(0, field.Len())
@@ -340,10 +408,11 @@ func isEmptyValue(v reflect.Value) bool {
 		return v.Uint() == 0
 	case reflect.Float32, reflect.Float64:
 		return v.Float() == 0
-	case reflectTime:
-		return v.Interface().(time.Time).IsZero()
 	case reflect.Interface, reflect.Ptr:
 		return v.IsNil()
+	case reflectTime:
+		t, ok := v.Interface().(time.Time)
+		return ok && t.IsZero()
 	}
 	return false
 }
@@ -381,6 +450,12 @@ func (s *Section) reflectFrom(val reflect.Value) error {
 				// Note: fieldName can never be empty here, ignore error.
 				sec, _ = s.f.NewSection(fieldName)
 			}
+
+			// Add comment from comment tag
+			if len(sec.Comment) == 0 {
+				sec.Comment = tpField.Tag.Get("comment")
+			}
+
 			if err = sec.reflectFrom(field); err != nil {
 				return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
 			}
@@ -392,6 +467,12 @@ func (s *Section) reflectFrom(val reflect.Value) error {
 		if err != nil {
 			key, _ = s.NewKey(fieldName, "")
 		}
+
+		// Add comment from comment tag
+		if len(key.Comment) == 0 {
+			key.Comment = tpField.Tag.Get("comment")
+		}
+
 		if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
 			return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
 		}

+ 3 - 3
vendor/vendor.json

@@ -609,10 +609,10 @@
 			"revisionTime": "2016-04-11T21:29:32Z"
 		},
 		{
-			"checksumSHA1": "/nX/at2Sz9tc1TJ/ay6J9or5Uzc=",
+			"checksumSHA1": "CbAUaj2BnT21xuHSo3IhdG3L2gU=",
 			"path": "gopkg.in/ini.v1",
-			"revision": "ee900ca565931451fe4e4409bcbd4316331cec1c",
-			"revisionTime": "2017-02-09T04:24:15Z"
+			"revision": "cec2bdc49009247305a260b082a18e802d0fe292",
+			"revisionTime": "2018-06-15T00:35:39Z"
 		},
 		{
 			"checksumSHA1": "7jPSjzw3mckHVQ2SjY4NvtIJR4g=",