Browse Source

feat(alerting): fixed s3 upload issue, progress on alerting on null/missing data, updated ini package to get the support for line continuations

Torkel Ödegaard 9 years ago
parent
commit
50b41130ca

+ 3 - 3
Godeps/Godeps.json

@@ -1,6 +1,6 @@
 {
 	"ImportPath": "github.com/grafana/grafana",
-	"GoVersion": "go1.5.1",
+	"GoVersion": "go1.6.2",
 	"GodepVersion": "v60",
 	"Packages": [
 		"./pkg/..."
@@ -368,8 +368,8 @@
 		},
 		{
 			"ImportPath": "gopkg.in/ini.v1",
-			"Comment": "v0-16-g1772191",
-			"Rev": "177219109c97e7920c933e21c9b25f874357b237"
+			"Comment": "v1.21.1",
+			"Rev": "6e4869b434bd001f6983749881c7ead3545887d8"
 		},
 		{
 			"ImportPath": "gopkg.in/macaron.v1",

+ 2 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore

@@ -1,3 +1,5 @@
 testdata/conf_out.ini
 ini.sublime-project
 ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea

+ 16 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/.travis.yml

@@ -0,0 +1,16 @@
+sudo: false
+language: go
+
+go:
+  - 1.4
+  - 1.5
+  - 1.6
+  - tip
+
+script: 
+  - go get -v github.com/smartystreets/goconvey
+  - go test -v -cover -race
+
+notifications:
+  email:
+    - u@gogs.io

+ 12 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/Makefile

@@ -0,0 +1,12 @@
+.PHONY: build test bench vet
+
+build: vet bench
+
+test:
+	go test -v -cover -race
+
+bench:
+	go test -v -cover -race -test.bench=. -test.benchmem
+
+vet:
+	go vet

+ 339 - 14
Godeps/_workspace/src/gopkg.in/ini.v1/README.md

@@ -1,6 +1,8 @@
-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)
+INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini)
 ===
 
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
 Package ini provides INI file read and write functionality in Go.
 
 [简体中文](README_ZH.md)
@@ -20,13 +22,29 @@ Package ini provides INI file read and write functionality in Go.
 
 ## Installation
 
+To use a tagged revision:
+
 	go get gopkg.in/ini.v1
 
+To use with latest changes:
+
+	go get github.com/go-ini/ini
+
+Please add `-u` flag to update in the future.
+
+### Testing
+
+If you want to test on your machine, please apply `-t` flag:
+
+	go get -t gopkg.in/ini.v1
+
+Please add `-u` flag to update in the future.
+
 ## Getting Started
 
 ### Loading from data sources
 
-A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many as** data sources you want. Passing other types will simply return an error.
+A **Data Source** is either raw data in type `[]byte` or a file name with type `string` and you can load **as many data sources as you want**. Passing other types will simply return an error.
 
 ```go
 cfg, err := ini.Load([]byte("raw data"), "filename")
@@ -38,12 +56,56 @@ Or start with an empty object:
 cfg := ini.Empty()
 ```
 
-When you cannot decide how many data sources to load at the beginning, you still able to **Append()** them later.
+When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
 
 ```go
 err := cfg.Append("other file", []byte("other raw data"))
 ```
 
+If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
+
+```go
+cfg, err := ini.LooseLoad("filename", "filename_404")
+```
+
+The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
+
+#### Ignore cases of key name
+
+When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
+
+```go
+cfg, err := ini.InsensitiveLoad("filename")
+//...
+
+// sec1 and sec2 are the exactly same section object
+sec1, err := cfg.GetSection("Section")
+sec2, err := cfg.GetSection("SecTIOn")
+
+// key1 and key2 are the exactly same key object
+key1, err := cfg.GetKey("Key")
+key2, err := cfg.GetKey("KeY")
+```
+
+#### MySQL-like boolean key 
+
+MySQL's configuration allows a key without value as follows:
+
+```ini
+[mysqld]
+...
+skip-host-cache
+skip-name-resolve
+```
+
+By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
+
+```go
+cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
+```
+
+The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
+
 ### Working with sections
 
 To get a section, you would need to:
@@ -93,6 +155,12 @@ Same rule applies to key operations:
 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
@@ -102,14 +170,14 @@ err := cfg.Section("").NewKey("name", "value")
 To get a list of keys or key names:
 
 ```go
-keys := cfg.Section().Keys()
-names := cfg.Section().KeyStrings()
+keys := cfg.Section("").Keys()
+names := cfg.Section("").KeyStrings()
 ```
 
 To get a clone hash of keys and corresponding values:
 
 ```go
-hash := cfg.GetSection("").KeysHash()
+hash := cfg.Section("").KeysHash()
 ```
 
 ### Working with values
@@ -120,16 +188,41 @@ To get a string value:
 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, ON, on, On
-// false when value is: 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
+// 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
 
@@ -137,6 +230,8 @@ 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
 
@@ -144,11 +239,13 @@ v = cfg.Section("").Key("TIME").MustTime() // RFC3339
 // 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.Seciont("").Key("String").MustString("default")
+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
 ```
@@ -174,6 +271,42 @@ 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
@@ -185,6 +318,8 @@ 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
 ```
@@ -197,20 +332,74 @@ To validate value in a given range:
 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:
+##### Auto-split values into a slice
+
+To use zero value of type for invalid inputs:
 
 ```go
+// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
+// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
 vals = cfg.Section("").Key("STRINGS").Strings(",")
 vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
 vals = cfg.Section("").Key("INTS").Ints(",")
 vals = cfg.Section("").Key("INT64S").Int64s(",")
+vals = cfg.Section("").Key("UINTS").Uints(",")
+vals = cfg.Section("").Key("UINT64S").Uint64s(",")
 vals = cfg.Section("").Key("TIMES").Times(",")
 ```
 
+To exclude invalid values out of result slice:
+
+```go
+// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
+// Input: how, 2.2, are, you -> [2.2]
+vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
+vals = cfg.Section("").Key("INTS").ValidInts(",")
+vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
+vals = cfg.Section("").Key("UINTS").ValidUints(",")
+vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
+vals = cfg.Section("").Key("TIMES").ValidTimes(",")
+```
+
+Or to return nothing but error when have invalid inputs:
+
+```go
+// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
+// Input: how, 2.2, are, you -> error
+vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
+vals = cfg.Section("").Key("INTS").StrictInts(",")
+vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
+vals = cfg.Section("").Key("UINTS").StrictUints(",")
+vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
+vals = cfg.Section("").Key("TIMES").StrictTimes(",")
+```
+
+### Save your configuration
+
+Finally, it's time to save your configuration to somewhere.
+
+A typical way to save configuration is writing it to a file:
+
+```go
+// ...
+err = cfg.SaveTo("my.ini")
+err = cfg.SaveToIndent("my.ini", "\t")
+```
+
+Another way to save is writing to a `io.Writer` interface:
+
+```go
+// ...
+cfg.WriteTo(writer)
+cfg.WriteToIndent(writer, "\t")
+```
+
 ## Advanced Usage
 
 ### Recursive Values
@@ -252,6 +441,12 @@ CLONE_URL = https://%(IMPORT_PATH)s
 cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
 ```
 
+#### Retrieve parent keys available to a child section
+
+```go
+cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
+```
+
 ### Auto-increment Key Names
 
 If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
@@ -327,9 +522,57 @@ p := &Person{
 // ...
 ```
 
+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 secion and key name.
+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:
 
@@ -339,15 +582,15 @@ There are 2 built-in name mappers:
 To use them:
 
 ```go
-type Info struct{
+type Info struct {
 	PackageName string
 }
 
 func main() {
-	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
+	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
 	// ...
 
-	cfg, err := ini.Load("PACKAGE_NAME=ini")
+	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
 	// ...
 	info := new(Info)
 	cfg.NameMapper = ini.AllCapsUnderscore
@@ -356,6 +599,88 @@ func main() {
 }
 ```
 
+Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
+
+#### Value Mapper
+
+To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
+
+```go
+type Env struct {
+	Foo string `ini:"foo"`
+}
+
+func main() {
+	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
+	cfg.ValueMapper = os.ExpandEnv
+	// ...
+	env := &Env{}
+	err = cfg.Section("env").MapTo(env)
+}
+```
+
+This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
+
+#### Other Notes On Map/Reflect
+
+Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
+
+```go
+type Child struct {
+	Age string
+}
+
+type Parent struct {
+	Name string
+	Child
+}
+
+type Config struct {
+	City string
+	Parent
+}
+```
+
+Example configuration:
+
+```ini
+City = Boston
+
+[Parent]
+Name = Unknwon
+
+[Child]
+Age = 21
+```
+
+What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
+
+```go
+type Child struct {
+	Age string
+}
+
+type Parent struct {
+	Name string
+	Child `ini:"Parent"`
+}
+
+type Config struct {
+	City string
+	Parent
+}
+```
+
+Example configuration:
+
+```ini
+City = Boston
+
+[Parent]
+Name = Unknwon
+Age = 21
+```
+
 ## Getting Help
 
 - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)

+ 331 - 8
Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md

@@ -15,8 +15,24 @@
 
 ## 下载安装
 
+使用一个特定版本:
+
     go get gopkg.in/ini.v1
 
+使用最新版:
+
+	go get github.com/go-ini/ini
+
+如需更新请添加 `-u` 选项。
+
+### 测试安装
+
+如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
+
+	go get -t gopkg.in/ini.v1
+
+如需更新请添加 `-u` 选项。
+
 ## 开始使用
 
 ### 从数据源加载
@@ -39,6 +55,50 @@ cfg := ini.Empty()
 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`,且在保存到文件时也只会输出键名。
+
 ### 操作分区(Section)
 
 获取指定分区:
@@ -88,6 +148,12 @@ key, err := cfg.Section("").GetKey("key name")
 key := cfg.Section("").Key("key name")
 ```
 
+判断某个键是否存在:
+
+```go
+yes := cfg.Section("").HasKey("key name")
+```
+
 创建一个新的键:
 
 ```go
@@ -97,14 +163,14 @@ err := cfg.Section("").NewKey("name", "value")
 获取分区下的所有键或键名:
 
 ```go
-keys := cfg.Section().Keys()
-names := cfg.Section().KeyStrings()
+keys := cfg.Section("").Keys()
+names := cfg.Section("").KeyStrings()
 ```
 
 获取分区下的所有键值对的克隆:
 
 ```go
-hash := cfg.GetSection("").KeysHash()
+hash := cfg.Section("").KeysHash()
 ```
 
 ### 操作键值(Value)
@@ -115,16 +181,41 @@ hash := cfg.GetSection("").KeysHash()
 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, ON, on, On
-// false 当值为:0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
+// 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
 
@@ -132,6 +223,8 @@ 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
 
@@ -144,6 +237,8 @@ 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
 ```
@@ -169,6 +264,42 @@ 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
+```
+
 这就是全部了?哈哈,当然不是。
 
 #### 操作键值的辅助方法
@@ -180,6 +311,8 @@ 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
 ```
@@ -192,20 +325,74 @@ v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, tim
 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):
+##### 自动分割键值到切片(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")
+```
+
 ### 高级用法
 
 #### 递归读取键值
@@ -247,6 +434,12 @@ CLONE_URL = https://%(IMPORT_PATH)s
 cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
 ```
 
+#### 获取上级父分区下的所有键名
+
+```go
+cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
+```
+
 #### 读取自增键名
 
 如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
@@ -320,6 +513,54 @@ p := &Person{
 // ...
 ```
 
+这样玩 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) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
@@ -337,10 +578,10 @@ type Info struct{
 }
 
 func main() {
-	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("packag_name=ini"))
+	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
 	// ...
 
-	cfg, err := ini.Load("PACKAGE_NAME=ini")
+	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
 	// ...
 	info := new(Info)
 	cfg.NameMapper = ini.AllCapsUnderscore
@@ -349,6 +590,88 @@ func main() {
 }
 ```
 
+使用函数 `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)

+ 32 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/error.go

@@ -0,0 +1,32 @@
+// Copyright 2016 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 (
+	"fmt"
+)
+
+type ErrDelimiterNotFound struct {
+	Line string
+}
+
+func IsErrDelimiterNotFound(err error) bool {
+	_, ok := err.(ErrDelimiterNotFound)
+	return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+	return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}

+ 177 - 693
Godeps/_workspace/src/gopkg.in/ini.v1/ini.go

@@ -16,7 +16,6 @@
 package ini
 
 import (
-	"bufio"
 	"bytes"
 	"errors"
 	"fmt"
@@ -31,25 +30,35 @@ import (
 )
 
 const (
+	// Name for default section. You can use this constant or the string literal.
+	// In most of cases, an empty string is all you need to access the section.
 	DEFAULT_SECTION = "DEFAULT"
+
 	// Maximum allowed depth when recursively substituing variable names.
 	_DEPTH_VALUES = 99
-
-	_VERSION = "1.2.6"
+	_VERSION      = "1.21.1"
 )
 
+// Version returns current package version literal.
 func Version() string {
 	return _VERSION
 }
 
 var (
+	// Delimiter to determine or compose a new line.
+	// This variable will be changed to "\r\n" automatically on Windows
+	// at package init time.
 	LineBreak = "\n"
 
 	// Variable regexp pattern: %(variable)s
 	varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
 
-	// Write spaces around "=" to look better.
+	// Indicate whether to align "=" sign with spaces to produce pretty output
+	// or reduce all possible spaces for compact format.
 	PrettyFormat = true
+
+	// Explicitly write DEFAULT section header
+	DefaultHeader = false
 )
 
 func init() {
@@ -67,501 +76,41 @@ func inSlice(str string, s []string) bool {
 	return false
 }
 
-// dataSource is a interface that returns file content.
+// dataSource is an interface that returns object which can be read and closed.
 type dataSource interface {
-	Reader() (io.Reader, error)
+	ReadCloser() (io.ReadCloser, error)
 }
 
+// sourceFile represents an object that contains content on the local file system.
 type sourceFile struct {
 	name string
 }
 
-func (s sourceFile) Reader() (io.Reader, error) {
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
 	return os.Open(s.name)
 }
 
-type sourceData struct {
-	data []byte
-}
-
-func (s *sourceData) Reader() (io.Reader, error) {
-	return bytes.NewReader(s.data), nil
-}
-
-//  ____  __.
-// |    |/ _|____ ___.__.
-// |      <_/ __ <   |  |
-// |    |  \  ___/\___  |
-// |____|__ \___  > ____|
-//         \/   \/\/
-
-// Key represents a key under a section.
-type Key struct {
-	s          *Section
-	Comment    string
-	name       string
-	value      string
-	isAutoIncr bool
-}
-
-// Name returns name of key.
-func (k *Key) Name() string {
-	return k.name
-}
-
-// Value returns raw value of key for performance purpose.
-func (k *Key) Value() string {
-	return k.value
-}
-
-// String returns string representation of value.
-func (k *Key) String() string {
-	val := k.value
-	if strings.Index(val, "%") == -1 {
-		return val
-	}
-
-	for i := 0; i < _DEPTH_VALUES; i++ {
-		vr := varPattern.FindString(val)
-		if len(vr) == 0 {
-			break
-		}
-
-		// Take off leading '%(' and trailing ')s'.
-		noption := strings.TrimLeft(vr, "%(")
-		noption = strings.TrimRight(noption, ")s")
-
-		// Search in the same section.
-		nk, err := k.s.GetKey(noption)
-		if err != nil {
-			// Search again in default section.
-			nk, _ = k.s.f.Section("").GetKey(noption)
-		}
-
-		// Substitute by new value and take off leading '%(' and trailing ')s'.
-		val = strings.Replace(val, vr, nk.value, -1)
-	}
-	return val
-}
-
-// parseBool returns the boolean value represented by the string.
-//
-// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On,
-// 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off.
-// Any other value returns an error.
-func parseBool(str string) (value bool, err error) {
-	switch str {
-	case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "ON", "on", "On":
-		return true, nil
-	case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "OFF", "off", "Off":
-		return false, nil
-	}
-	return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
-}
-
-// Bool returns bool type value.
-func (k *Key) Bool() (bool, error) {
-	return parseBool(k.String())
-}
-
-// Float64 returns float64 type value.
-func (k *Key) Float64() (float64, error) {
-	return strconv.ParseFloat(k.String(), 64)
-}
-
-// Int returns int type value.
-func (k *Key) Int() (int, error) {
-	return strconv.Atoi(k.String())
-}
-
-// Int64 returns int64 type value.
-func (k *Key) Int64() (int64, error) {
-	return strconv.ParseInt(k.String(), 10, 64)
-}
-
-// TimeFormat parses with given format and returns time.Time type value.
-func (k *Key) TimeFormat(format string) (time.Time, error) {
-	return time.Parse(format, k.String())
-}
-
-// Time parses with RFC3339 format and returns time.Time type value.
-func (k *Key) Time() (time.Time, error) {
-	return k.TimeFormat(time.RFC3339)
-}
-
-// MustString returns default value if key value is empty.
-func (k *Key) MustString(defaultVal string) string {
-	val := k.String()
-	if len(val) == 0 {
-		return defaultVal
-	}
-	return val
-}
-
-// MustBool always returns value without error,
-// it returns false if error occurs.
-func (k *Key) MustBool(defaultVal ...bool) bool {
-	val, err := k.Bool()
-	if len(defaultVal) > 0 && err != nil {
-		return defaultVal[0]
-	}
-	return val
-}
-
-// MustFloat64 always returns value without error,
-// it returns 0.0 if error occurs.
-func (k *Key) MustFloat64(defaultVal ...float64) float64 {
-	val, err := k.Float64()
-	if len(defaultVal) > 0 && err != nil {
-		return defaultVal[0]
-	}
-	return val
-}
-
-// MustInt always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt(defaultVal ...int) int {
-	val, err := k.Int()
-	if len(defaultVal) > 0 && err != nil {
-		return defaultVal[0]
-	}
-	return val
-}
-
-// MustInt64 always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt64(defaultVal ...int64) int64 {
-	val, err := k.Int64()
-	if len(defaultVal) > 0 && err != nil {
-		return defaultVal[0]
-	}
-	return val
-}
-
-// MustTimeFormat always parses with given format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
-	val, err := k.TimeFormat(format)
-	if len(defaultVal) > 0 && err != nil {
-		return defaultVal[0]
-	}
-	return val
-}
-
-// MustTime always parses with RFC3339 format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
-	return k.MustTimeFormat(time.RFC3339, defaultVal...)
-}
-
-// In always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) In(defaultVal string, candidates []string) string {
-	val := k.String()
-	for _, cand := range candidates {
-		if val == cand {
-			return val
-		}
-	}
-	return defaultVal
-}
-
-// InFloat64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
-	val := k.MustFloat64()
-	for _, cand := range candidates {
-		if val == cand {
-			return val
-		}
-	}
-	return defaultVal
-}
-
-// InInt always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt(defaultVal int, candidates []int) int {
-	val := k.MustInt()
-	for _, cand := range candidates {
-		if val == cand {
-			return val
-		}
-	}
-	return defaultVal
-}
-
-// InInt64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
-	val := k.MustInt64()
-	for _, cand := range candidates {
-		if val == cand {
-			return val
-		}
-	}
-	return defaultVal
-}
-
-// InTimeFormat always parses with given format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
-	val := k.MustTimeFormat(format)
-	for _, cand := range candidates {
-		if val == cand {
-			return val
-		}
-	}
-	return defaultVal
-}
-
-// InTime always parses with RFC3339 format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
-	return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
-}
-
-// RangeFloat64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
-	val := k.MustFloat64()
-	if val < min || val > max {
-		return defaultVal
-	}
-	return val
-}
-
-// RangeInt checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt(defaultVal, min, max int) int {
-	val := k.MustInt()
-	if val < min || val > max {
-		return defaultVal
-	}
-	return val
-}
-
-// RangeInt64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
-	val := k.MustInt64()
-	if val < min || val > max {
-		return defaultVal
-	}
-	return val
-}
-
-// RangeTimeFormat checks if value with given format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
-	val := k.MustTimeFormat(format)
-	if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
-		return defaultVal
-	}
-	return val
-}
-
-// RangeTime checks if value with RFC3339 format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
-	return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
-}
-
-// Strings returns list of string devide by given delimiter.
-func (k *Key) Strings(delim string) []string {
-	str := k.String()
-	if len(str) == 0 {
-		return []string{}
-	}
-
-	vals := strings.Split(str, delim)
-	for i := range vals {
-		vals[i] = strings.TrimSpace(vals[i])
-	}
-	return vals
-}
-
-// Float64s returns list of float64 devide by given delimiter.
-func (k *Key) Float64s(delim string) []float64 {
-	strs := k.Strings(delim)
-	vals := make([]float64, len(strs))
-	for i := range strs {
-		vals[i], _ = strconv.ParseFloat(strs[i], 64)
-	}
-	return vals
-}
-
-// Ints returns list of int devide by given delimiter.
-func (k *Key) Ints(delim string) []int {
-	strs := k.Strings(delim)
-	vals := make([]int, len(strs))
-	for i := range strs {
-		vals[i], _ = strconv.Atoi(strs[i])
-	}
-	return vals
-}
-
-// Int64s returns list of int64 devide by given delimiter.
-func (k *Key) Int64s(delim string) []int64 {
-	strs := k.Strings(delim)
-	vals := make([]int64, len(strs))
-	for i := range strs {
-		vals[i], _ = strconv.ParseInt(strs[i], 10, 64)
-	}
-	return vals
-}
-
-// TimesFormat parses with given format and returns list of time.Time devide by given delimiter.
-func (k *Key) TimesFormat(format, delim string) []time.Time {
-	strs := k.Strings(delim)
-	vals := make([]time.Time, len(strs))
-	for i := range strs {
-		vals[i], _ = time.Parse(format, strs[i])
-	}
-	return vals
-}
-
-// Times parses with RFC3339 format and returns list of time.Time devide by given delimiter.
-func (k *Key) Times(delim string) []time.Time {
-	return k.TimesFormat(time.RFC3339, delim)
-}
-
-// SetValue changes key value.
-func (k *Key) SetValue(v string) {
-	k.value = v
-}
-
-//   _________              __  .__
-//  /   _____/ ____   _____/  |_|__| ____   ____
-//  \_____  \_/ __ \_/ ___\   __\  |/  _ \ /    \
-//  /        \  ___/\  \___|  | |  (  <_> )   |  \
-// /_______  /\___  >\___  >__| |__|\____/|___|  /
-//         \/     \/     \/                    \/
-
-// Section represents a config section.
-type Section struct {
-	f        *File
-	Comment  string
-	name     string
-	keys     map[string]*Key
-	keyList  []string
-	keysHash map[string]string
-}
-
-func newSection(f *File, name string) *Section {
-	return &Section{f, "", name, make(map[string]*Key), make([]string, 0, 10), make(map[string]string)}
-}
-
-// Name returns name of Section.
-func (s *Section) Name() string {
-	return s.name
-}
-
-// NewKey creates a new key to given section.
-func (s *Section) NewKey(name, val string) (*Key, error) {
-	if len(name) == 0 {
-		return nil, errors.New("error creating new key: empty key name")
-	}
-
-	if s.f.BlockMode {
-		s.f.lock.Lock()
-		defer s.f.lock.Unlock()
-	}
-
-	if inSlice(name, s.keyList) {
-		s.keys[name].value = val
-		return s.keys[name], nil
-	}
-
-	s.keyList = append(s.keyList, name)
-	s.keys[name] = &Key{s, "", name, val, false}
-	s.keysHash[name] = val
-	return s.keys[name], nil
-}
-
-// GetKey returns key in section by given name.
-func (s *Section) GetKey(name string) (*Key, error) {
-	// FIXME: change to section level lock?
-	if s.f.BlockMode {
-		s.f.lock.RLock()
-		defer s.f.lock.RUnlock()
-	}
-
-	key := s.keys[name]
-	if key == nil {
-		// Check if it is a child-section.
-		if i := strings.LastIndex(s.name, "."); i > -1 {
-			return s.f.Section(s.name[:i]).GetKey(name)
-		}
-		return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
-	}
-	return key, nil
-}
-
-// Key assumes named Key exists in section and returns a zero-value when not.
-func (s *Section) Key(name string) *Key {
-	key, err := s.GetKey(name)
-	if err != nil {
-		// It's OK here because the only possible error is empty key name,
-		// but if it's empty, this piece of code won't be executed.
-		key, _ = s.NewKey(name, "")
-		return key
-	}
-	return key
+type bytesReadCloser struct {
+	reader io.Reader
 }
 
-// Keys returns list of keys of section.
-func (s *Section) Keys() []*Key {
-	keys := make([]*Key, len(s.keyList))
-	for i := range s.keyList {
-		keys[i] = s.Key(s.keyList[i])
-	}
-	return keys
+func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
+	return rc.reader.Read(p)
 }
 
-// KeyStrings returns list of key names of section.
-func (s *Section) KeyStrings() []string {
-	list := make([]string, len(s.keyList))
-	copy(list, s.keyList)
-	return list
+func (rc *bytesReadCloser) Close() error {
+	return nil
 }
 
-// KeysHash returns keys hash consisting of names and values.
-func (s *Section) KeysHash() map[string]string {
-	if s.f.BlockMode {
-		s.f.lock.RLock()
-		defer s.f.lock.RUnlock()
-	}
-
-	hash := map[string]string{}
-	for key, value := range s.keysHash {
-		hash[key] = value
-	}
-	return hash
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+	data []byte
 }
 
-// DeleteKey deletes a key from section.
-func (s *Section) DeleteKey(name string) {
-	if s.f.BlockMode {
-		s.f.lock.Lock()
-		defer s.f.lock.Unlock()
-	}
-
-	for i, k := range s.keyList {
-		if k == name {
-			s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
-			delete(s.keys, name)
-			return
-		}
-	}
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+	return &bytesReadCloser{bytes.NewReader(s.data)}, 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.
@@ -577,16 +126,20 @@ type File struct {
 	// To keep data in order.
 	sectionList []string
 
+	options LoadOptions
+
 	NameMapper
+	ValueMapper
 }
 
 // newFile initializes File object with given data sources.
-func newFile(dataSources []dataSource) *File {
+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,
 	}
 }
 
@@ -601,9 +154,19 @@ func parseDataSource(source interface{}) (dataSource, error) {
 	}
 }
 
-// Load loads and parses from INI data sources.
-// Arguments can be mixed of file name with string type, or raw data in []byte.
-func Load(source interface{}, others ...interface{}) (_ *File, err error) {
+type LoadOptions struct {
+	// Loose indicates whether the parser should ignore nonexistent files or return error.
+	Loose bool
+	// Insensitive indicates whether the parser forces all section and key names to lowercase.
+	Insensitive bool
+	// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+	IgnoreContinuation 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
+}
+
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
 	sources := make([]dataSource, len(others)+1)
 	sources[0], err = parseDataSource(source)
 	if err != nil {
@@ -615,8 +178,30 @@ func Load(source interface{}, others ...interface{}) (_ *File, err error) {
 			return nil, err
 		}
 	}
-	f := newFile(sources)
-	return f, f.Reload()
+	f := newFile(sources, opts)
+	if err = f.Reload(); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+	return LoadSources(LoadOptions{Insensitive: true}, source, others...)
 }
 
 // Empty returns an empty file object.
@@ -630,6 +215,8 @@ func Empty() *File {
 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 {
@@ -660,6 +247,8 @@ func (f *File) NewSections(names ...string) (err error) {
 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 {
@@ -669,7 +258,7 @@ func (f *File) GetSection(name string) (*Section, error) {
 
 	sec := f.sections[name]
 	if sec == nil {
-		return nil, fmt.Errorf("error when getting section: section '%s' not exists", name)
+		return nil, fmt.Errorf("section '%s' does not exist", name)
 	}
 	return sec, nil
 }
@@ -678,7 +267,7 @@ func (f *File) GetSection(name string) (*Section, error) {
 func (f *File) Section(name string) *Section {
 	sec, err := f.GetSection(name)
 	if err != nil {
-		// It's OK here because the only possible error is empty section name,
+		// 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
@@ -722,200 +311,25 @@ func (f *File) DeleteSection(name string) {
 	}
 }
 
-func cutComment(str string) string {
-	i := strings.Index(str, "#")
-	if i == -1 {
-		return str
-	}
-	return str[:i]
-}
-
-// parse parses data through an io.Reader.
-func (f *File) parse(reader io.Reader) error {
-	buf := bufio.NewReader(reader)
-
-	// Handle BOM-UTF8.
-	// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
-	mask, err := buf.Peek(3)
-	if err == nil && len(mask) >= 3 && mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
-		buf.Read(mask)
-	}
-
-	count := 1
-	comments := ""
-	isEnd := false
-
-	section, err := f.NewSection(DEFAULT_SECTION)
+func (f *File) reload(s dataSource) error {
+	r, err := s.ReadCloser()
 	if err != nil {
 		return err
 	}
+	defer r.Close()
 
-	for {
-		line, err := buf.ReadString('\n')
-		line = strings.TrimSpace(line)
-		length := len(line)
-
-		// Check error and ignore io.EOF just for a moment.
-		if err != nil {
-			if err != io.EOF {
-				return fmt.Errorf("error reading next line: %v", err)
-			}
-			// The last line of file could be an empty line.
-			if length == 0 {
-				break
-			}
-			isEnd = true
-		}
-
-		// Skip empty lines.
-		if length == 0 {
-			continue
-		}
-
-		switch {
-		case line[0] == '#' || line[0] == ';': // Comments.
-			if len(comments) == 0 {
-				comments = line
-			} else {
-				comments += LineBreak + line
-			}
-			continue
-		case line[0] == '[' && line[length-1] == ']': // New sction.
-			name := strings.TrimSpace(line[1 : length-1])
-			section, err = f.NewSection(name)
-			if err != nil {
-				return err
-			}
-
-			if len(comments) > 0 {
-				section.Comment = comments
-				comments = ""
-			}
-			// Reset counter.
-			count = 1
-			continue
-		}
-
-		// Other possibilities.
-		var (
-			i        int
-			keyQuote string
-			kname    string
-			valQuote string
-			val      string
-		)
-
-		// Key name surrounded by quotes.
-		if line[0] == '"' {
-			if length > 6 && line[0:3] == `"""` {
-				keyQuote = `"""`
-			} else {
-				keyQuote = `"`
-			}
-		} else if line[0] == '`' {
-			keyQuote = "`"
-		}
-		if len(keyQuote) > 0 {
-			qLen := len(keyQuote)
-			pos := strings.Index(line[qLen:], keyQuote)
-			if pos == -1 {
-				return fmt.Errorf("error parsing line: missing closing key quote: %s", line)
-			}
-			pos = pos + qLen
-			i = strings.IndexAny(line[pos:], "=:")
-			if i < 0 {
-				return fmt.Errorf("error parsing line: key-value delimiter not found: %s", line)
-			} else if i == pos {
-				return fmt.Errorf("error parsing line: key is empty: %s", line)
-			}
-			i = i + pos
-			kname = line[qLen:pos] // Just keep spaces inside quotes.
-		} else {
-			i = strings.IndexAny(line, "=:")
-			if i < 0 {
-				return fmt.Errorf("error parsing line: key-value delimiter not found: %s", line)
-			} else if i == 0 {
-				return fmt.Errorf("error parsing line: key is empty: %s", line)
-			}
-			kname = strings.TrimSpace(line[0:i])
-		}
-
-		isAutoIncr := false
-		// Auto increment.
-		if kname == "-" {
-			isAutoIncr = true
-			kname = "#" + fmt.Sprint(count)
-			count++
-		}
-
-		lineRight := strings.TrimSpace(line[i+1:])
-		lineRightLength := len(lineRight)
-		firstChar := ""
-		if lineRightLength >= 2 {
-			firstChar = lineRight[0:1]
-		}
-		if firstChar == "`" {
-			valQuote = "`"
-		} else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
-			valQuote = `"""`
-		}
-		if len(valQuote) > 0 {
-			qLen := len(valQuote)
-			pos := strings.LastIndex(lineRight[qLen:], valQuote)
-			// For multiple lines value.
-			if pos == -1 {
-				isEnd := false
-				val = lineRight[qLen:] + "\n"
-				for {
-					next, err := buf.ReadString('\n')
-					if err != nil {
-						if err != io.EOF {
-							return err
-						}
-						isEnd = true
-					}
-					pos = strings.LastIndex(next, valQuote)
-					if pos > -1 {
-						val += next[:pos]
-						break
-					}
-					val += next
-					if isEnd {
-						return fmt.Errorf("error parsing line: missing closing key quote from '%s' to '%s'", line, next)
-					}
-				}
-			} else {
-				val = lineRight[qLen : pos+qLen]
-			}
-		} else {
-			val = strings.TrimSpace(cutComment(lineRight[0:]))
-		}
-
-		k, err := section.NewKey(kname, val)
-		if err != nil {
-			return err
-		}
-		k.isAutoIncr = isAutoIncr
-		if len(comments) > 0 {
-			k.Comment = comments
-			comments = ""
-		}
-
-		if isEnd {
-			break
-		}
-	}
-	return nil
+	return f.parse(r)
 }
 
 // Reload reloads and parses all data sources.
-func (f *File) Reload() error {
+func (f *File) Reload() (err error) {
 	for _, s := range f.dataSources {
-		r, err := s.Reader()
-		if err != nil {
-			return err
-		}
-		if err = f.parse(r); err != nil {
+		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
 		}
 	}
@@ -939,8 +353,10 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
 	return f.Reload()
 }
 
-// SaveTo writes content to filesystem.
-func (f *File) SaveTo(filename string) (err error) {
+// 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 = " = "
@@ -955,63 +371,131 @@ func (f *File) SaveTo(filename string) (err error) {
 				sec.Comment = "; " + sec.Comment
 			}
 			if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
-				return err
+				return 0, err
 			}
 		}
 
-		if i > 0 {
+		if i > 0 || DefaultHeader {
 			if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
-				return err
+				return 0, err
 			}
 		} else {
-			// Write nothing if default section is empty.
+			// Write nothing if default section is empty
 			if len(sec.keyList) == 0 {
 				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 err
+					return 0, err
 				}
 			}
 
+			if len(indent) > 0 && sname != DEFAULT_SECTION {
+				buf.WriteString(indent)
+			}
+
 			switch {
-			case key.isAutoIncr:
+			case key.isAutoIncrement:
 				kname = "-"
-			case strings.Contains(kname, "`") || strings.Contains(kname, `"`):
-				kname = `"""` + kname + `"""`
-			case strings.Contains(kname, `=`) || strings.Contains(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 {
+				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.Contains(val, "\n") || strings.Contains(val, "`") || strings.Contains(val, `"`) {
+			// 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(kname + equalSign + val + LineBreak); err != nil {
-				return err
+			if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
+				return 0, err
 			}
 		}
 
-		// Put a line between sections.
+		// Put a line between sections
 		if _, err = buf.WriteString(LineBreak); err != nil {
-			return err
+			return 0, err
 		}
 	}
 
-	fw, err := os.Create(filename)
+	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 = buf.WriteTo(fw); err != nil {
+
+	if _, err = f.WriteToIndent(fw, indent); err != nil {
+		fw.Close()
 		return err
 	}
-	return fw.Close()
+	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, "")
 }

+ 0 - 456
Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go

@@ -1,456 +0,0 @@
-// 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. 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 (
-	"fmt"
-	"strings"
-	"testing"
-	"time"
-
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-func Test_Version(t *testing.T) {
-	Convey("Get version", t, func() {
-		So(Version(), ShouldEqual, _VERSION)
-	})
-}
-
-const _CONF_DATA = `
-; Package name
-NAME = ini
-; Package version
-VERSION = v1
-; Package import path
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-# Information about package author
-# Bio can be written in multiple lines.
-[author]
-NAME = Unknwon  # Succeeding comment
-E-MAIL = fake@localhost
-GITHUB = https://github.com/%(NAME)s
-BIO = """Gopher.
-Coding addict.
-Good man.
-"""  # Succeeding comment
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-UNUSED_KEY = should be deleted
-
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-
-[types]
-STRING = str
-BOOL = true
-BOOL_FALSE = false
-FLOAT64 = 1.25
-INT = 10
-TIME = 2015-01-01T20:17:05Z
-
-[array]
-STRINGS = en, zh, de
-FLOAT64S = 1.1, 2.2, 3.3
-INTS = 1, 2, 3
-TIMES = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
-
-[note]
-
-[advance]
-true = """"2+3=5""""
-"1+1=2" = true
-"""6+1=7""" = true
-"""` + "`" + `5+5` + "`" + `""" = 10
-""""6+6"""" = 12
-` + "`" + `7-2=4` + "`" + ` = false
-ADDRESS = ` + "`" + `404 road,
-NotFound, State, 50000` + "`"
-
-func Test_Load(t *testing.T) {
-	Convey("Load from data sources", t, func() {
-
-		Convey("Load with empty data", func() {
-			So(Empty(), ShouldNotBeNil)
-		})
-
-		Convey("Load with multiple data sources", func() {
-			cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
-			So(err, ShouldBeNil)
-			So(cfg, ShouldNotBeNil)
-		})
-	})
-
-	Convey("Bad load process", t, func() {
-
-		Convey("Load from invalid data sources", func() {
-			_, err := Load(_CONF_DATA)
-			So(err, ShouldNotBeNil)
-
-			_, err = Load("testdata/404.ini")
-			So(err, ShouldNotBeNil)
-
-			_, err = Load(1)
-			So(err, ShouldNotBeNil)
-
-			_, err = Load([]byte(""), 1)
-			So(err, ShouldNotBeNil)
-		})
-
-		Convey("Load with empty section name", func() {
-			_, err := Load([]byte("[]"))
-			So(err, ShouldNotBeNil)
-		})
-
-		Convey("Load with bad keys", func() {
-			_, err := Load([]byte(`"""name`))
-			So(err, ShouldNotBeNil)
-
-			_, err = Load([]byte(`"""name"""`))
-			So(err, ShouldNotBeNil)
-
-			_, err = Load([]byte(`""=1`))
-			So(err, ShouldNotBeNil)
-
-			_, err = Load([]byte(`=`))
-			So(err, ShouldNotBeNil)
-
-			_, err = Load([]byte(`name`))
-			So(err, ShouldNotBeNil)
-		})
-
-		Convey("Load with bad values", func() {
-			_, err := Load([]byte(`name="""Unknwon`))
-			So(err, ShouldNotBeNil)
-		})
-	})
-}
-
-func Test_Values(t *testing.T) {
-	Convey("Test getting and setting values", t, func() {
-		cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		Convey("Get values in default section", func() {
-			sec := cfg.Section("")
-			So(sec, ShouldNotBeNil)
-			So(sec.Key("NAME").Value(), ShouldEqual, "ini")
-			So(sec.Key("NAME").String(), ShouldEqual, "ini")
-			So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
-			So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
-		})
-
-		Convey("Get values in non-default section", func() {
-			sec := cfg.Section("author")
-			So(sec, ShouldNotBeNil)
-			So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
-			So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon")
-
-			sec = cfg.Section("package")
-			So(sec, ShouldNotBeNil)
-			So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
-		})
-
-		Convey("Get auto-increment key names", func() {
-			keys := cfg.Section("features").Keys()
-			for i, k := range keys {
-				So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1))
-			}
-		})
-
-		Convey("Get overwrite value", func() {
-			So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
-		})
-
-		Convey("Get sections", func() {
-			sections := cfg.Sections()
-			for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "advance"} {
-				So(sections[i].Name(), ShouldEqual, name)
-			}
-		})
-
-		Convey("Get parent section value", func() {
-			So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
-		})
-
-		Convey("Get multiple line value", func() {
-			So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
-		})
-
-		Convey("Get values with type", func() {
-			sec := cfg.Section("types")
-			v1, err := sec.Key("BOOL").Bool()
-			So(err, ShouldBeNil)
-			So(v1, ShouldBeTrue)
-
-			v1, err = sec.Key("BOOL_FALSE").Bool()
-			So(err, ShouldBeNil)
-			So(v1, ShouldBeFalse)
-
-			v2, err := sec.Key("FLOAT64").Float64()
-			So(err, ShouldBeNil)
-			So(v2, ShouldEqual, 1.25)
-
-			v3, err := sec.Key("INT").Int()
-			So(err, ShouldBeNil)
-			So(v3, ShouldEqual, 10)
-
-			v4, err := sec.Key("INT").Int64()
-			So(err, ShouldBeNil)
-			So(v4, ShouldEqual, 10)
-
-			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
-			So(err, ShouldBeNil)
-			v5, err := sec.Key("TIME").Time()
-			So(err, ShouldBeNil)
-			So(v5.String(), ShouldEqual, t.String())
-
-			Convey("Must get values with type", func() {
-				So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
-				So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
-				So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
-				So(sec.Key("INT").MustInt(), ShouldEqual, 10)
-				So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
-				So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
-
-				Convey("Must get values with default value", func() {
-					So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
-					So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
-					So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
-					So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
-					So(sec.Key("INT_404").MustInt64(15), ShouldEqual, 15)
-
-					t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
-					So(err, ShouldBeNil)
-					So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
-				})
-			})
-		})
-
-		Convey("Get value with candidates", func() {
-			sec := cfg.Section("types")
-			So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str")
-			So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
-			So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10)
-			So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
-
-			zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
-			So(err, ShouldBeNil)
-			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
-			So(err, ShouldBeNil)
-			So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
-
-			Convey("Get value with candidates and default value", func() {
-				So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
-				So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
-				So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
-				So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
-				So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
-			})
-		})
-
-		Convey("Get values in range", func() {
-			sec := cfg.Section("types")
-			So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
-			So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
-			So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
-
-			minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
-			So(err, ShouldBeNil)
-			midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
-			So(err, ShouldBeNil)
-			maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
-			So(err, ShouldBeNil)
-			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
-			So(err, ShouldBeNil)
-			So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
-
-			Convey("Get value in range with default value", func() {
-				So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
-				So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
-				So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
-				So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
-			})
-		})
-
-		Convey("Get values into slice", func() {
-			sec := cfg.Section("array")
-			So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
-			So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
-
-			vals1 := sec.Key("FLOAT64S").Float64s(",")
-			for i, v := range []float64{1.1, 2.2, 3.3} {
-				So(vals1[i], ShouldEqual, v)
-			}
-
-			vals2 := sec.Key("INTS").Ints(",")
-			for i, v := range []int{1, 2, 3} {
-				So(vals2[i], ShouldEqual, v)
-			}
-
-			vals3 := sec.Key("INTS").Int64s(",")
-			for i, v := range []int64{1, 2, 3} {
-				So(vals3[i], ShouldEqual, v)
-			}
-
-			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
-			So(err, ShouldBeNil)
-			vals4 := sec.Key("TIMES").Times(",")
-			for i, v := range []time.Time{t, t, t} {
-				So(vals4[i].String(), ShouldEqual, v.String())
-			}
-		})
-
-		Convey("Get key hash", func() {
-			cfg.Section("").KeysHash()
-		})
-
-		Convey("Set key value", func() {
-			k := cfg.Section("author").Key("NAME")
-			k.SetValue("无闻")
-			So(k.String(), ShouldEqual, "无闻")
-		})
-
-		Convey("Get key strings", func() {
-			So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME")
-		})
-
-		Convey("Delete a key", func() {
-			cfg.Section("package.sub").DeleteKey("UNUSED_KEY")
-			_, err := cfg.Section("package.sub").GetKey("UNUSED_KEY")
-			So(err, ShouldNotBeNil)
-		})
-
-		Convey("Get section strings", func() {
-			So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,advance")
-		})
-
-		Convey("Delete a section", func() {
-			cfg.DeleteSection("")
-			So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
-		})
-
-		Convey("Create new sections", func() {
-			cfg.NewSections("test", "test2")
-			_, err := cfg.GetSection("test")
-			So(err, ShouldBeNil)
-			_, err = cfg.GetSection("test2")
-			So(err, ShouldBeNil)
-		})
-	})
-
-	Convey("Test getting and setting bad values", t, func() {
-		cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		Convey("Create new key with empty name", func() {
-			k, err := cfg.Section("").NewKey("", "")
-			So(err, ShouldNotBeNil)
-			So(k, ShouldBeNil)
-		})
-
-		Convey("Create new section with empty name", func() {
-			s, err := cfg.NewSection("")
-			So(err, ShouldNotBeNil)
-			So(s, ShouldBeNil)
-		})
-
-		Convey("Create new sections with empty name", func() {
-			So(cfg.NewSections(""), ShouldNotBeNil)
-		})
-
-		Convey("Get section that not exists", func() {
-			s, err := cfg.GetSection("404")
-			So(err, ShouldNotBeNil)
-			So(s, ShouldBeNil)
-
-			s = cfg.Section("404")
-			So(s, ShouldNotBeNil)
-		})
-	})
-}
-
-func Test_File_Append(t *testing.T) {
-	Convey("Append data sources", t, func() {
-		cfg, err := Load([]byte(""))
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		So(cfg.Append([]byte(""), []byte("")), ShouldBeNil)
-
-		Convey("Append bad data sources", func() {
-			So(cfg.Append(1), ShouldNotBeNil)
-			So(cfg.Append([]byte(""), 1), ShouldNotBeNil)
-		})
-	})
-}
-
-func Test_File_SaveTo(t *testing.T) {
-	Convey("Save file", t, func() {
-		cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		cfg.Section("").Key("NAME").Comment = "Package name"
-		cfg.Section("author").Comment = `Information about package author
-# Bio can be written in multiple lines.`
-		So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil)
-	})
-}
-
-func Benchmark_Key_Value(b *testing.B) {
-	c, _ := Load([]byte(_CONF_DATA))
-	for i := 0; i < b.N; i++ {
-		c.Section("").Key("NAME").Value()
-	}
-}
-
-func Benchmark_Key_String(b *testing.B) {
-	c, _ := Load([]byte(_CONF_DATA))
-	for i := 0; i < b.N; i++ {
-		c.Section("").Key("NAME").String()
-	}
-}
-
-func Benchmark_Key_Value_NonBlock(b *testing.B) {
-	c, _ := Load([]byte(_CONF_DATA))
-	c.BlockMode = false
-	for i := 0; i < b.N; i++ {
-		c.Section("").Key("NAME").Value()
-	}
-}
-
-func Benchmark_Key_String_NonBlock(b *testing.B) {
-	c, _ := Load([]byte(_CONF_DATA))
-	c.BlockMode = false
-	for i := 0; i < b.N; i++ {
-		c.Section("").Key("NAME").String()
-	}
-}
-
-func Benchmark_Key_SetValue(b *testing.B) {
-	c, _ := Load([]byte(_CONF_DATA))
-	for i := 0; i < b.N; i++ {
-		c.Section("").Key("NAME").SetValue("10")
-	}
-}

+ 633 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/key.go

@@ -0,0 +1,633 @@
+// 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. 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 (
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+	s               *Section
+	name            string
+	value           string
+	isAutoIncrement bool
+	isBooleanType   bool
+
+	Comment string
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+	return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+	return k.value
+}
+
+// String returns string representation of value.
+func (k *Key) String() string {
+	val := k.value
+	if k.s.f.ValueMapper != nil {
+		val = k.s.f.ValueMapper(val)
+	}
+	if strings.Index(val, "%") == -1 {
+		return val
+	}
+
+	for i := 0; i < _DEPTH_VALUES; i++ {
+		vr := varPattern.FindString(val)
+		if len(vr) == 0 {
+			break
+		}
+
+		// Take off leading '%(' and trailing ')s'.
+		noption := strings.TrimLeft(vr, "%(")
+		noption = strings.TrimRight(noption, ")s")
+
+		// Search in the same section.
+		nk, err := k.s.GetKey(noption)
+		if err != nil {
+			// Search again in default section.
+			nk, _ = k.s.f.Section("").GetKey(noption)
+		}
+
+		// Substitute by new value and take off leading '%(' and trailing ')s'.
+		val = strings.Replace(val, vr, nk.value, -1)
+	}
+	return val
+}
+
+// Validate accepts a validate function which can
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+	return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+	switch str {
+	case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+		return true, nil
+	case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+		return false, nil
+	}
+	return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+	return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+	return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+	return strconv.Atoi(k.String())
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+	return strconv.ParseInt(k.String(), 10, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+	u, e := strconv.ParseUint(k.String(), 10, 64)
+	return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+	return strconv.ParseUint(k.String(), 10, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+	return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+	return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+	return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+	val := k.String()
+	if len(val) == 0 {
+		k.value = defaultVal
+		return defaultVal
+	}
+	return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+	val, err := k.Bool()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatBool(defaultVal[0])
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+	val, err := k.Float64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+	val, err := k.Int()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+	val, err := k.Int64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatInt(defaultVal[0], 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+	val, err := k.Uint()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+	val, err := k.Uint64()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = strconv.FormatUint(defaultVal[0], 10)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+	val, err := k.Duration()
+	if len(defaultVal) > 0 && err != nil {
+		k.value = defaultVal[0].String()
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+	val, err := k.TimeFormat(format)
+	if len(defaultVal) > 0 && err != nil {
+		k.value = defaultVal[0].Format(format)
+		return defaultVal[0]
+	}
+	return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+	return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+	val := k.String()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+	val := k.MustFloat64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+	val := k.MustInt()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+	val := k.MustInt64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+	val := k.MustUint()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+	val := k.MustUint64()
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+	val := k.MustTimeFormat(format)
+	for _, cand := range candidates {
+		if val == cand {
+			return val
+		}
+	}
+	return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+	return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+	val := k.MustFloat64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+	val := k.MustInt()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+	val := k.MustInt64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+	val := k.MustTimeFormat(format)
+	if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+	return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+	str := k.String()
+	if len(str) == 0 {
+		return []string{}
+	}
+
+	vals := strings.Split(str, delim)
+	for i := range vals {
+		vals[i] = strings.TrimSpace(vals[i])
+	}
+	return vals
+}
+
+// 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)
+	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)
+	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)
+	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)
+	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)
+	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)
+	return vals
+}
+
+// Times parses with RFC3339 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) Times(delim string) []time.Time {
+	return k.TimesFormat(time.RFC3339, delim)
+}
+
+// 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)
+	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)
+	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)
+	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)
+	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)
+	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)
+	return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+	return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// 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)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+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)
+	vals := make([]float64, 0, len(strs))
+	for _, str := range strs {
+		val, err := strconv.ParseFloat(str, 64)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	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)
+	vals := make([]int, 0, len(strs))
+	for _, str := range strs {
+		val, err := strconv.Atoi(str)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	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)
+	vals := make([]int64, 0, len(strs))
+	for _, str := range strs {
+		val, err := strconv.ParseInt(str, 10, 64)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	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)
+	vals := make([]uint, 0, len(strs))
+	for _, str := range strs {
+		val, err := strconv.ParseUint(str, 10, 0)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, uint(val))
+		}
+	}
+	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)
+	vals := make([]uint64, 0, len(strs))
+	for _, str := range strs {
+		val, err := strconv.ParseUint(str, 10, 64)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	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)
+	vals := make([]time.Time, 0, len(strs))
+	for _, str := range strs {
+		val, err := time.Parse(format, str)
+		if err != nil && returnOnInvalid {
+			return nil, err
+		}
+		if err == nil || addInvalid {
+			vals = append(vals, val)
+		}
+	}
+	return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+	if k.s.f.BlockMode {
+		k.s.f.lock.Lock()
+		defer k.s.f.lock.Unlock()
+	}
+
+	k.value = v
+	k.s.keysHash[k.name] = v
+}

+ 325 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/parser.go

@@ -0,0 +1,325 @@
+// Copyright 2015 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 (
+	"bufio"
+	"bytes"
+	"fmt"
+	"io"
+	"strconv"
+	"strings"
+	"unicode"
+)
+
+type tokenType int
+
+const (
+	_TOKEN_INVALID tokenType = iota
+	_TOKEN_COMMENT
+	_TOKEN_SECTION
+	_TOKEN_KEY
+)
+
+type parser struct {
+	buf     *bufio.Reader
+	isEOF   bool
+	count   int
+	comment *bytes.Buffer
+}
+
+func newParser(r io.Reader) *parser {
+	return &parser{
+		buf:     bufio.NewReader(r),
+		count:   1,
+		comment: &bytes.Buffer{},
+	}
+}
+
+// BOM handles header of BOM-UTF8 format.
+// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
+func (p *parser) BOM() error {
+	mask, err := p.buf.Peek(3)
+	if err != nil && err != io.EOF {
+		return err
+	} else if len(mask) < 3 {
+		return nil
+	} else if mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
+		p.buf.Read(mask)
+	}
+	return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+	data, err := p.buf.ReadBytes(delim)
+	if err != nil {
+		if err == io.EOF {
+			p.isEOF = true
+		} else {
+			return nil, err
+		}
+	}
+	return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+	i := bytes.IndexAny(in, "#;")
+	if i == -1 {
+		return nil, false
+	}
+	return in[i:], true
+}
+
+func readKeyName(in []byte) (string, int, error) {
+	line := string(in)
+
+	// Check if key name surrounded by quotes.
+	var keyQuote string
+	if line[0] == '"' {
+		if len(line) > 6 && string(line[0:3]) == `"""` {
+			keyQuote = `"""`
+		} else {
+			keyQuote = `"`
+		}
+	} else if line[0] == '`' {
+		keyQuote = "`"
+	}
+
+	// Get out key name
+	endIdx := -1
+	if len(keyQuote) > 0 {
+		startIdx := len(keyQuote)
+		// FIXME: fail case -> """"""name"""=value
+		pos := strings.Index(line[startIdx:], keyQuote)
+		if pos == -1 {
+			return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+		}
+		pos += startIdx
+
+		// Find key-value delimiter
+		i := strings.IndexAny(line[pos+startIdx:], "=:")
+		if i < 0 {
+			return "", -1, ErrDelimiterNotFound{line}
+		}
+		endIdx = pos + i
+		return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+	}
+
+	endIdx = strings.IndexAny(line, "=:")
+	if endIdx < 0 {
+		return "", -1, ErrDelimiterNotFound{line}
+	}
+	return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+	for {
+		data, err := p.readUntil('\n')
+		if err != nil {
+			return "", err
+		}
+		next := string(data)
+
+		pos := strings.LastIndex(next, valQuote)
+		if pos > -1 {
+			val += next[:pos]
+
+			comment, has := cleanComment([]byte(next[pos:]))
+			if has {
+				p.comment.Write(bytes.TrimSpace(comment))
+			}
+			break
+		}
+		val += next
+		if p.isEOF {
+			return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
+		}
+	}
+	return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+	for {
+		data, err := p.readUntil('\n')
+		if err != nil {
+			return "", err
+		}
+		next := strings.TrimSpace(string(data))
+
+		if len(next) == 0 {
+			break
+		}
+		val += next
+		if val[len(val)-1] != '\\' {
+			break
+		}
+		val = val[:len(val)-1]
+	}
+	return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// 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 &&
+		strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
+	line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+	if len(line) == 0 {
+		return "", nil
+	}
+
+	var valQuote string
+	if len(line) > 3 && string(line[0:3]) == `"""` {
+		valQuote = `"""`
+	} else if line[0] == '`' {
+		valQuote = "`"
+	}
+
+	if len(valQuote) > 0 {
+		startIdx := len(valQuote)
+		pos := strings.LastIndex(line[startIdx:], valQuote)
+		// Check for multi-line value
+		if pos == -1 {
+			return p.readMultilines(line, line[startIdx:], valQuote)
+		}
+
+		return line[startIdx : pos+startIdx], nil
+	}
+
+	// Won't be able to reach here if value only contains whitespace.
+	line = strings.TrimSpace(line)
+
+	// Check continuation lines when desired.
+	if !ignoreContinuation && line[len(line)-1] == '\\' {
+		return p.readContinuationLines(line[:len(line)-1])
+	}
+
+	i := strings.IndexAny(line, "#;")
+	if i > -1 {
+		p.comment.WriteString(line[i:])
+		line = strings.TrimSpace(line[:i])
+	}
+
+	// Trim single quotes
+	if hasSurroundedQuote(line, '\'') ||
+		hasSurroundedQuote(line, '"') {
+		line = line[1 : len(line)-1]
+	}
+	return line, nil
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+	p := newParser(reader)
+	if err = p.BOM(); err != nil {
+		return fmt.Errorf("BOM: %v", err)
+	}
+
+	// Ignore error because default section name is never empty string.
+	section, _ := f.NewSection(DEFAULT_SECTION)
+
+	var line []byte
+	for !p.isEOF {
+		line, err = p.readUntil('\n')
+		if err != nil {
+			return err
+		}
+
+		line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+		if len(line) == 0 {
+			continue
+		}
+
+		// Comments
+		if line[0] == '#' || line[0] == ';' {
+			// Note: we do not care ending line break,
+			// it is needed for adding second line,
+			// so just clean it once at the end when set to value.
+			p.comment.Write(line)
+			continue
+		}
+
+		// Section
+		if line[0] == '[' {
+			// Read to the next ']' (TODO: support quoted strings)
+			// TODO(unknwon): use LastIndexByte when stop supporting Go1.4
+			closeIdx := bytes.LastIndex(line, []byte("]"))
+			if closeIdx == -1 {
+				return fmt.Errorf("unclosed section: %s", line)
+			}
+
+			name := string(line[1:closeIdx])
+			section, err = f.NewSection(name)
+			if err != nil {
+				return err
+			}
+
+			comment, has := cleanComment(line[closeIdx+1:])
+			if has {
+				p.comment.Write(comment)
+			}
+
+			section.Comment = strings.TrimSpace(p.comment.String())
+
+			// Reset aotu-counter and comments
+			p.comment.Reset()
+			p.count = 1
+			continue
+		}
+
+		kname, offset, err := readKeyName(line)
+		if err != nil {
+			// Treat as boolean key when desired, and whole line is key name.
+			if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
+				key, err := section.NewKey(string(line), "true")
+				if err != nil {
+					return err
+				}
+				key.isBooleanType = true
+				key.Comment = strings.TrimSpace(p.comment.String())
+				p.comment.Reset()
+				continue
+			}
+			return err
+		}
+
+		// Auto increment.
+		isAutoIncr := false
+		if kname == "-" {
+			isAutoIncr = true
+			kname = "#" + strconv.Itoa(p.count)
+			p.count++
+		}
+
+		key, err := section.NewKey(kname, "")
+		if err != nil {
+			return err
+		}
+		key.isAutoIncrement = isAutoIncr
+
+		value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
+		if err != nil {
+			return err
+		}
+		key.SetValue(value)
+		key.Comment = strings.TrimSpace(p.comment.String())
+		p.comment.Reset()
+	}
+	return nil
+}

+ 206 - 0
Godeps/_workspace/src/gopkg.in/ini.v1/section.go

@@ -0,0 +1,206 @@
+// 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. 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 (
+	"errors"
+	"fmt"
+	"strings"
+)
+
+// Section represents a config section.
+type Section struct {
+	f        *File
+	Comment  string
+	name     string
+	keys     map[string]*Key
+	keyList  []string
+	keysHash map[string]string
+}
+
+func newSection(f *File, name string) *Section {
+	return &Section{f, "", name, make(map[string]*Key), make([]string, 0, 10), make(map[string]string)}
+}
+
+// Name returns name of Section.
+func (s *Section) Name() string {
+	return s.name
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+	if len(name) == 0 {
+		return nil, errors.New("error creating new key: empty key name")
+	} else if s.f.options.Insensitive {
+		name = strings.ToLower(name)
+	}
+
+	if s.f.BlockMode {
+		s.f.lock.Lock()
+		defer s.f.lock.Unlock()
+	}
+
+	if inSlice(name, s.keyList) {
+		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.keysHash[name] = val
+	return s.keys[name], nil
+}
+
+// GetKey returns key in section by given name.
+func (s *Section) GetKey(name string) (*Key, error) {
+	// FIXME: change to section level lock?
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+	}
+	if s.f.options.Insensitive {
+		name = strings.ToLower(name)
+	}
+	key := s.keys[name]
+	if s.f.BlockMode {
+		s.f.lock.RUnlock()
+	}
+
+	if key == nil {
+		// Check if it is a child-section.
+		sname := s.name
+		for {
+			if i := strings.LastIndex(sname, "."); i > -1 {
+				sname = sname[:i]
+				sec, err := s.f.GetSection(sname)
+				if err != nil {
+					continue
+				}
+				return sec.GetKey(name)
+			} else {
+				break
+			}
+		}
+		return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
+	}
+	return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+	key, _ := s.GetKey(name)
+	return key != nil
+}
+
+// Haskey is a backwards-compatible name for HasKey.
+func (s *Section) Haskey(name string) bool {
+	return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+		defer s.f.lock.RUnlock()
+	}
+
+	for _, k := range s.keys {
+		if value == k.value {
+			return true
+		}
+	}
+	return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+	key, err := s.GetKey(name)
+	if err != nil {
+		// It's OK here because the only possible error is empty key name,
+		// but if it's empty, this piece of code won't be executed.
+		key, _ = s.NewKey(name, "")
+		return key
+	}
+	return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+	keys := make([]*Key, len(s.keyList))
+	for i := range s.keyList {
+		keys[i] = s.Key(s.keyList[i])
+	}
+	return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+	var parentKeys []*Key
+	sname := s.name
+	for {
+		if i := strings.LastIndex(sname, "."); i > -1 {
+			sname = sname[:i]
+			sec, err := s.f.GetSection(sname)
+			if err != nil {
+				continue
+			}
+			parentKeys = append(parentKeys, sec.Keys()...)
+		} else {
+			break
+		}
+
+	}
+	return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+	list := make([]string, len(s.keyList))
+	copy(list, s.keyList)
+	return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+	if s.f.BlockMode {
+		s.f.lock.RLock()
+		defer s.f.lock.RUnlock()
+	}
+
+	hash := map[string]string{}
+	for key, value := range s.keysHash {
+		hash[key] = value
+	}
+	return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+	if s.f.BlockMode {
+		s.f.lock.Lock()
+		defer s.f.lock.Unlock()
+	}
+
+	for i, k := range s.keyList {
+		if k == name {
+			s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+			delete(s.keys, name)
+			return
+		}
+	}
+}

+ 248 - 37
Godeps/_workspace/src/gopkg.in/ini.v1/struct.go

@@ -15,9 +15,11 @@
 package ini
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"reflect"
+	"strings"
 	"time"
 	"unicode"
 )
@@ -75,11 +77,64 @@ 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)
+	numVals := len(strs)
+	if numVals == 0 {
+		return nil
+	}
+
+	var vals interface{}
+
+	sliceOf := field.Type().Elem().Kind()
+	switch sliceOf {
+	case reflect.String:
+		vals = strs
+	case reflect.Int:
+		vals = key.Ints(delim)
+	case reflect.Int64:
+		vals = key.Int64s(delim)
+	case reflect.Uint:
+		vals = key.Uints(delim)
+	case reflect.Uint64:
+		vals = key.Uint64s(delim)
+	case reflect.Float64:
+		vals = key.Float64s(delim)
+	case reflectTime:
+		vals = key.Times(delim)
+	default:
+		return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+	}
+
+	slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+	for i := 0; i < numVals; i++ {
+		switch sliceOf {
+		case reflect.String:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+		case reflect.Int:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+		case reflect.Int64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+		case reflect.Uint:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+		case reflect.Uint64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+		case reflect.Float64:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+		case reflectTime:
+			slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+		}
+	}
+	field.Set(slice)
+	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(kind reflect.Kind, key *Key, field reflect.Value, delim string) error {
-	switch kind {
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
+	switch t.Kind() {
 	case reflect.String:
 		if len(key.String()) == 0 {
 			return nil
@@ -92,11 +147,33 @@ func setWithProperType(kind reflect.Kind, key *Key, field reflect.Value, delim s
 		}
 		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 {
+			field.Set(reflect.ValueOf(durationVal))
+			return nil
+		}
+
 		intVal, err := key.Int64()
-		if err != nil {
+		if err != nil || intVal == 0 {
 			return nil
 		}
 		field.SetInt(intVal)
+	//	byte is an alias for uint8, so supporting uint8 breaks support for byte
+	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		durationVal, err := key.Duration()
+		// Skip zero value
+		if err == nil && int(durationVal) > 0 {
+			field.Set(reflect.ValueOf(durationVal))
+			return nil
+		}
+
+		uintVal, err := key.Uint64()
+		if err != nil {
+			return nil
+		}
+		field.SetUint(uintVal)
+
 	case reflect.Float64:
 		floatVal, err := key.Float64()
 		if err != nil {
@@ -110,31 +187,9 @@ func setWithProperType(kind reflect.Kind, key *Key, field reflect.Value, delim s
 		}
 		field.Set(reflect.ValueOf(timeVal))
 	case reflect.Slice:
-		vals := key.Strings(delim)
-		numVals := len(vals)
-		if numVals == 0 {
-			return nil
-		}
-
-		sliceOf := field.Type().Elem().Kind()
-
-		var times []time.Time
-		if sliceOf == reflectTime {
-			times = key.Times(delim)
-		}
-
-		slice := reflect.MakeSlice(field.Type(), numVals, numVals)
-		for i := 0; i < numVals; i++ {
-			switch sliceOf {
-			case reflectTime:
-				slice.Index(i).Set(reflect.ValueOf(times[i]))
-			default:
-				slice.Index(i).Set(reflect.ValueOf(vals[i]))
-			}
-		}
-		field.Set(slice)
+		return setSliceWithProperType(key, field, delim)
 	default:
-		return fmt.Errorf("unsupported type '%s'", kind)
+		return fmt.Errorf("unsupported type '%s'", t)
 	}
 	return nil
 }
@@ -154,20 +209,19 @@ func (s *Section) mapTo(val reflect.Value) error {
 			continue
 		}
 
-		fieldName := s.parseFieldName(tpField.Name, tag)
+		opts := strings.SplitN(tag, ",", 2) // strip off possible omitempty
+		fieldName := s.parseFieldName(tpField.Name, opts[0])
 		if len(fieldName) == 0 || !field.CanSet() {
 			continue
 		}
 
-		if tpField.Type.Kind() == reflect.Struct {
-			if sec, err := s.f.GetSection(fieldName); err == nil {
-				if err = sec.mapTo(field); err != nil {
-					return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
-				}
-				continue
-			}
-		} else if tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous {
+		isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+		isStruct := tpField.Type.Kind() == reflect.Struct
+		if isAnonymous {
 			field.Set(reflect.New(tpField.Type.Elem()))
+		}
+
+		if isAnonymous || isStruct {
 			if sec, err := s.f.GetSection(fieldName); err == nil {
 				if err = sec.mapTo(field); err != nil {
 					return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
@@ -177,7 +231,7 @@ func (s *Section) mapTo(val reflect.Value) error {
 		}
 
 		if key, err := s.GetKey(fieldName); err == nil {
-			if err = setWithProperType(tpField.Type.Kind(), key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
+			if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
 				return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
 			}
 		}
@@ -218,3 +272,160 @@ func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, other
 func MapTo(v, source interface{}, others ...interface{}) error {
 	return MapToWithMapper(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())
+	if field.Len() == 0 {
+		return nil
+	}
+
+	var buf bytes.Buffer
+	sliceOf := field.Type().Elem().Kind()
+	for i := 0; i < field.Len(); i++ {
+		switch sliceOf {
+		case reflect.String:
+			buf.WriteString(slice.Index(i).String())
+		case reflect.Int, reflect.Int64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+		case reflect.Uint, reflect.Uint64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+		case reflect.Float64:
+			buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+		case reflectTime:
+			buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+		default:
+			return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+		}
+		buf.WriteString(delim)
+	}
+	key.SetValue(buf.String()[:buf.Len()-1])
+	return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
+	switch t.Kind() {
+	case reflect.String:
+		key.SetValue(field.String())
+	case reflect.Bool:
+		key.SetValue(fmt.Sprint(field.Bool()))
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		key.SetValue(fmt.Sprint(field.Int()))
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		key.SetValue(fmt.Sprint(field.Uint()))
+	case reflect.Float32, reflect.Float64:
+		key.SetValue(fmt.Sprint(field.Float()))
+	case reflectTime:
+		key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+	case reflect.Slice:
+		return reflectSliceWithProperType(key, field, delim)
+	default:
+		return fmt.Errorf("unsupported type '%s'", t)
+	}
+	return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+		return v.Len() == 0
+	case reflect.Bool:
+		return !v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() == 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		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()
+	}
+	return false
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+	if val.Kind() == reflect.Ptr {
+		val = val.Elem()
+	}
+	typ := val.Type()
+
+	for i := 0; i < typ.NumField(); i++ {
+		field := val.Field(i)
+		tpField := typ.Field(i)
+
+		tag := tpField.Tag.Get("ini")
+		if tag == "-" {
+			continue
+		}
+
+		opts := strings.SplitN(tag, ",", 2)
+		if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
+			continue
+		}
+
+		fieldName := s.parseFieldName(tpField.Name, opts[0])
+		if len(fieldName) == 0 || !field.CanSet() {
+			continue
+		}
+
+		if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
+			(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+			// Note: The only error here is section doesn't exist.
+			sec, err := s.f.GetSection(fieldName)
+			if err != nil {
+				// Note: fieldName can never be empty here, ignore error.
+				sec, _ = s.f.NewSection(fieldName)
+			}
+			if err = sec.reflectFrom(field); err != nil {
+				return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
+			}
+			continue
+		}
+
+		// Note: Same reason as secion.
+		key, err := s.GetKey(fieldName)
+		if err != nil {
+			key, _ = s.NewKey(fieldName, "")
+		}
+		if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
+			return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
+		}
+
+	}
+	return nil
+}
+
+// ReflectFrom reflects secion from given struct.
+func (s *Section) ReflectFrom(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 reflect from non-pointer struct")
+	}
+
+	return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+	return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+	cfg.NameMapper = mapper
+	return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+	return ReflectFromWithMapper(cfg, v, nil)
+}

+ 0 - 181
Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go

@@ -1,181 +0,0 @@
-// 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. 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 (
-	"strings"
-	"testing"
-	"time"
-
-	. "github.com/smartystreets/goconvey/convey"
-)
-
-type testNested struct {
-	Cities []string `delim:"|"`
-	Visits []time.Time
-	Note   string
-	Unused int `ini:"-"`
-}
-
-type testEmbeded struct {
-	GPA float64
-}
-
-type testStruct struct {
-	Name         string `ini:"NAME"`
-	Age          int
-	Male         bool
-	Money        float64
-	Born         time.Time
-	Others       testNested
-	*testEmbeded `ini:"grade"`
-	Unused       int `ini:"-"`
-}
-
-const _CONF_DATA_STRUCT = `
-NAME = Unknwon
-Age = 21
-Male = true
-Money = 1.25
-Born = 1993-10-07T20:17:05Z
-
-[Others]
-Cities = HangZhou|Boston
-Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
-Note = Hello world!
-
-[grade]
-GPA = 2.8
-`
-
-type unsupport struct {
-	Byte byte
-}
-
-type unsupport2 struct {
-	Others struct {
-		Cities byte
-	}
-}
-
-type unsupport3 struct {
-	Cities byte
-}
-
-type unsupport4 struct {
-	*unsupport3 `ini:"Others"`
-}
-
-type defaultValue struct {
-	Name   string
-	Age    int
-	Male   bool
-	Money  float64
-	Born   time.Time
-	Cities []string
-}
-
-const _INVALID_DATA_CONF_STRUCT = `
-Name = 
-Age = age
-Male = 123
-Money = money
-Born = nil
-Cities = 
-`
-
-func Test_Struct(t *testing.T) {
-	Convey("Map file to struct", t, func() {
-		ts := new(testStruct)
-		So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil)
-
-		So(ts.Name, ShouldEqual, "Unknwon")
-		So(ts.Age, ShouldEqual, 21)
-		So(ts.Male, ShouldBeTrue)
-		So(ts.Money, ShouldEqual, 1.25)
-
-		t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
-		So(err, ShouldBeNil)
-		So(ts.Born.String(), ShouldEqual, t.String())
-
-		So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
-		So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
-		So(ts.Others.Note, ShouldEqual, "Hello world!")
-		So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
-	})
-
-	Convey("Map to non-pointer struct", t, func() {
-		cfg, err := Load([]byte(_CONF_DATA_STRUCT))
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		So(cfg.MapTo(testStruct{}), ShouldNotBeNil)
-	})
-
-	Convey("Map to unsupported type", t, func() {
-		cfg, err := Load([]byte(_CONF_DATA_STRUCT))
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		cfg.NameMapper = func(raw string) string {
-			if raw == "Byte" {
-				return "NAME"
-			}
-			return raw
-		}
-		So(cfg.MapTo(&unsupport{}), ShouldNotBeNil)
-		So(cfg.MapTo(&unsupport2{}), ShouldNotBeNil)
-		So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil)
-	})
-
-	Convey("Map from invalid data source", t, func() {
-		So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
-	})
-
-	Convey("Map to wrong types and gain default values", t, func() {
-		cfg, err := Load([]byte(_INVALID_DATA_CONF_STRUCT))
-		So(err, ShouldBeNil)
-
-		t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
-		So(err, ShouldBeNil)
-		dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
-		So(cfg.MapTo(dv), ShouldBeNil)
-		So(dv.Name, ShouldEqual, "Joe")
-		So(dv.Age, ShouldEqual, 10)
-		So(dv.Male, ShouldBeTrue)
-		So(dv.Money, ShouldEqual, 1.25)
-		So(dv.Born.String(), ShouldEqual, t.String())
-		So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
-	})
-}
-
-type testMapper struct {
-	PackageName string
-}
-
-func Test_NameGetter(t *testing.T) {
-	Convey("Test name mappers", t, func() {
-		So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
-
-		cfg, err := Load([]byte("PACKAGE_NAME=ini"))
-		So(err, ShouldBeNil)
-		So(cfg, ShouldNotBeNil)
-
-		cfg.NameMapper = AllCapsUnderscore
-		tg := new(testMapper)
-		So(cfg.MapTo(tg), ShouldBeNil)
-		So(tg.PackageName, ShouldEqual, "ini")
-	})
-}

+ 0 - 2
Godeps/_workspace/src/gopkg.in/ini.v1/testdata/conf.ini

@@ -1,2 +0,0 @@
-[author]
-E-MAIL = u@gogs.io

+ 3 - 3
pkg/components/imguploader/imguploader.go

@@ -19,9 +19,9 @@ func NewImageUploader() (ImageUploader, error) {
 			return nil, err
 		}
 
-		bucket := s3sec.Key("secret_key").String()
-		accessKey := s3sec.Key("access_key").String()
-		secretKey := s3sec.Key("secret_key").String()
+		bucket := s3sec.Key("bucket_url").MustString("")
+		accessKey := s3sec.Key("access_key").MustString("")
+		secretKey := s3sec.Key("secret_key").MustString("")
 
 		if bucket == "" {
 			return nil, fmt.Errorf("Could not find bucket setting for image.uploader.s3")

+ 21 - 5
pkg/components/imguploader/s3uploader.go

@@ -3,7 +3,10 @@ package imguploader
 import (
 	"io/ioutil"
 	"net/http"
+	"net/url"
+	"path"
 
+	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/util"
 	"github.com/kr/s3/s3util"
 )
@@ -12,6 +15,7 @@ type S3Uploader struct {
 	bucket    string
 	secretKey string
 	accessKey string
+	log       log.Logger
 }
 
 func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader {
@@ -19,10 +23,11 @@ func NewS3Uploader(bucket, accessKey, secretKey string) *S3Uploader {
 		bucket:    bucket,
 		accessKey: accessKey,
 		secretKey: secretKey,
+		log:       log.New("s3uploader"),
 	}
 }
 
-func (u *S3Uploader) Upload(path string) (string, error) {
+func (u *S3Uploader) Upload(imageDiskPath string) (string, error) {
 
 	s3util.DefaultConfig.AccessKey = u.accessKey
 	s3util.DefaultConfig.SecretKey = u.secretKey
@@ -31,15 +36,26 @@ func (u *S3Uploader) Upload(path string) (string, error) {
 	header.Add("x-amz-acl", "public-read")
 	header.Add("Content-Type", "image/png")
 
-	fullUrl := u.bucket + util.GetRandomString(20) + ".png"
-	writer, err := s3util.Create(fullUrl, header, nil)
+	var imageUrl *url.URL
+	var err error
+
+	if imageUrl, err = url.Parse(u.bucket); err != nil {
+		return "", err
+	}
+
+	// add image to url
+	imageUrl.Path = path.Join(imageUrl.Path, util.GetRandomString(20)+".png")
+	imageUrlString := imageUrl.String()
+	log.Debug("Uploading image to s3", "url", imageUrlString)
+
+	writer, err := s3util.Create(imageUrlString, header, nil)
 	if err != nil {
 		return "", err
 	}
 
 	defer writer.Close()
 
-	imgData, err := ioutil.ReadFile(path)
+	imgData, err := ioutil.ReadFile(imageDiskPath)
 	if err != nil {
 		return "", err
 	}
@@ -49,5 +65,5 @@ func (u *S3Uploader) Upload(path string) (string, error) {
 		return "", err
 	}
 
-	return fullUrl, nil
+	return imageUrlString, nil
 }

+ 8 - 0
pkg/services/alerting/conditions/evaluator.go

@@ -44,6 +44,10 @@ func newThresholdEvaludator(typ string, model *simplejson.Json) (*ThresholdEvalu
 }
 
 func (e *ThresholdEvaluator) Eval(reducedValue *float64) bool {
+	if reducedValue == nil {
+		return false
+	}
+
 	switch e.Type {
 	case "gt":
 		return *reducedValue > e.Threshold
@@ -83,6 +87,10 @@ func newRangedEvaluator(typ string, model *simplejson.Json) (*RangedEvaluator, e
 }
 
 func (e *RangedEvaluator) Eval(reducedValue *float64) bool {
+	if reducedValue == nil {
+		return false
+	}
+
 	switch e.Type {
 	case "within_range":
 		return (e.Lower < *reducedValue && e.Upper > *reducedValue) || (e.Upper < *reducedValue && e.Lower > *reducedValue)

+ 32 - 16
pkg/services/alerting/conditions/reducer.go

@@ -1,6 +1,10 @@
 package conditions
 
-import "github.com/grafana/grafana/pkg/tsdb"
+import (
+	"math"
+
+	"github.com/grafana/grafana/pkg/tsdb"
+)
 
 type QueryReducer interface {
 	Reduce(timeSeries *tsdb.TimeSeries) *float64
@@ -15,41 +19,53 @@ func (s *SimpleReducer) Reduce(series *tsdb.TimeSeries) *float64 {
 		return nil
 	}
 
-	var value float64 = 0
+	value := float64(0)
+	allNull := true
 
 	switch s.Type {
 	case "avg":
 		for _, point := range series.Points {
-			value += point[0]
+			if point[0] != nil {
+				value += *point[0]
+				allNull = false
+			}
 		}
 		value = value / float64(len(series.Points))
 	case "sum":
 		for _, point := range series.Points {
-			value += point[0]
+			if point[0] != nil {
+				value += *point[0]
+				allNull = false
+			}
 		}
 	case "min":
-		for i, point := range series.Points {
-			if i == 0 {
-				value = point[0]
-			}
-
-			if value > point[0] {
-				value = point[0]
+		value = math.MaxFloat64
+		for _, point := range series.Points {
+			if point[0] != nil {
+				allNull = false
+				if value > *point[0] {
+					value = *point[0]
+				}
 			}
 		}
 	case "max":
+		value = -math.MaxFloat64
 		for _, point := range series.Points {
-			if value < point[0] {
-				value = point[0]
+			if point[0] != nil {
+				allNull = false
+				if value < *point[0] {
+					value = *point[0]
+				}
 			}
 		}
-	case "mean":
-		meanPosition := int64(len(series.Points) / 2)
-		value = series.Points[meanPosition][0]
 	case "count":
 		value = float64(len(series.Points))
 	}
 
+	if allNull {
+		return nil
+	}
+
 	return &value
 }
 

+ 17 - 12
pkg/services/alerting/eval_context.go

@@ -37,31 +37,36 @@ type StateDescription struct {
 }
 
 func (c *EvalContext) GetStateModel() *StateDescription {
-	if c.Error != nil {
-		return &StateDescription{
-			Color: "#D63232",
-			Text:  "EXECUTION ERROR",
-		}
-	}
-
-	if !c.Firing {
+	switch c.Rule.State {
+	case m.AlertStateOK:
 		return &StateDescription{
 			Color: "#36a64f",
 			Text:  "OK",
 		}
-	}
-
-	if c.Rule.Severity == m.AlertSeverityWarning {
+	case m.AlertStateUnknown:
+		return &StateDescription{
+			Color: "#888888",
+			Text:  "UNKNOWN",
+		}
+	case m.AlertStateExeuctionError:
+		return &StateDescription{
+			Color: "#000",
+			Text:  "EXECUTION_ERROR",
+		}
+	case m.AlertStateWarning:
 		return &StateDescription{
 			Color: "#fd821b",
 			Text:  "WARNING",
 		}
-	} else {
+	case m.AlertStateCritical:
 		return &StateDescription{
 			Color: "#D63232",
 			Text:  "CRITICAL",
 		}
+	default:
+		panic("Unknown rule state " + c.Rule.State)
 	}
+
 }
 
 func (a *EvalContext) GetDurationMs() float64 {

+ 9 - 0
pkg/services/alerting/eval_handler.go

@@ -54,6 +54,15 @@ func (e *DefaultEvalHandler) retry(context *EvalContext) {
 }
 
 func (e *DefaultEvalHandler) eval(context *EvalContext) {
+	defer func() {
+		if err := recover(); err != nil {
+			e.log.Error("Alerting rule eval panic", "error", err, "stack", log.Stack(1))
+			if panicErr, ok := err.(error); ok {
+				context.Error = panicErr
+			}
+		}
+	}()
+
 	for _, condition := range context.Rule.Conditions {
 		condition.Eval(context)
 

+ 2 - 2
pkg/tsdb/graphite/types.go

@@ -1,6 +1,6 @@
 package graphite
 
 type TargetResponseDTO struct {
-	Target     string       `json:"target"`
-	DataPoints [][2]float64 `json:"datapoints"`
+	Target     string        `json:"target"`
+	DataPoints [][2]*float64 `json:"datapoints"`
 }

+ 3 - 3
pkg/tsdb/models.go

@@ -46,13 +46,13 @@ type QueryResult struct {
 }
 
 type TimeSeries struct {
-	Name   string       `json:"name"`
-	Points [][2]float64 `json:"points"`
+	Name   string        `json:"name"`
+	Points [][2]*float64 `json:"points"`
 }
 
 type TimeSeriesSlice []*TimeSeries
 
-func NewTimeSeries(name string, points [][2]float64) *TimeSeries {
+func NewTimeSeries(name string, points [][2]*float64) *TimeSeries {
 	return &TimeSeries{
 		Name:   name,
 		Points: points,