diff --git a/.travis.yml b/.travis.yml
index b097527..c8ea49c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,17 @@
 sudo: false
 language: go
 go:
-  - 1.5.x
   - 1.6.x
   - 1.7.x
   - 1.8.x
   - 1.9.x
+  - 1.10.x
+  - 1.11.x
 
-script: 
+script:
   - go get golang.org/x/tools/cmd/cover
   - go get github.com/smartystreets/goconvey
   - mkdir -p $HOME/gopath/src/gopkg.in
   - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1
+  - cd $HOME/gopath/src/gopkg.in/ini.v1
   - go test -v -cover -race
diff --git a/Makefile b/Makefile
index 1316911..af27ff0 100644
--- a/Makefile
+++ b/Makefile
@@ -12,4 +12,4 @@ vet:
 	go vet
 
 coverage:
-	go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
\ No newline at end of file
+	go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/README.md b/README.md
index f4ff27c..ae4dfc3 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,13 @@
-INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://sourcegraph.com/github.com/go-ini/ini/-/badge.svg)](https://sourcegraph.com/github.com/go-ini/ini?badge)
+INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/github.com/go-ini/ini)
 ===
 
 ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
 
 Package ini provides INI file read and write functionality in Go.
 
-[简体中文](README_ZH.md)
+## Features
 
-## Feature
-
-- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
+- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
 - Read with recursion values.
 - Read with parent-child sections.
 - Read with auto-increment key names.
@@ -22,741 +20,26 @@ 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`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
-
-```go
-cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
-```
-
-Or start with an empty object:
-
-```go
-cfg := ini.Empty()
-```
-
-When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
-
-```go
-err := cfg.Append("other file", []byte("other raw data"))
-```
-
-If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
-
-```go
-cfg, err := ini.LooseLoad("filename", "filename_404")
-```
-
-The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
-
-#### Ignore cases of key name
-
-When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
-
-```go
-cfg, err := ini.InsensitiveLoad("filename")
-//...
-
-// sec1 and sec2 are the exactly same section object
-sec1, err := cfg.GetSection("Section")
-sec2, err := cfg.GetSection("SecTIOn")
-
-// key1 and key2 are the exactly same key object
-key1, err := sec1.GetKey("Key")
-key2, err := sec2.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 := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
-```
-
-The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
-
-To generate such keys in your program, you could use `NewBooleanKey`:
-
-```go
-key, err := sec.NewBooleanKey("skip-host-cache")
-```
-
-#### Comment
-
-Take care that following format will be treated as comment:
-
-1. Line begins with `#` or `;`
-2. Words after `#` or `;`
-3. Words after section name (i.e words after `[some section name]`)
-
-If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
-
-Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, "app.ini"))
-```
-
-### Working with sections
-
-To get a section, you would need to:
-
-```go
-section, err := cfg.GetSection("section name")
-```
-
-For a shortcut for default section, just give an empty string as name:
-
-```go
-section, err := cfg.GetSection("")
-```
-
-When you're pretty sure the section exists, following code could make your life easier:
-
-```go
-section := cfg.Section("section name")
-```
-
-What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
-
-To create a new section:
-
-```go
-err := cfg.NewSection("new section")
-```
-
-To get a list of sections or section names:
-
-```go
-sections := cfg.Sections()
-names := cfg.SectionStrings()
-```
-
-### Working with keys
-
-To get a key under a section:
-
-```go
-key, err := cfg.Section("").GetKey("key name")
-```
-
-Same rule applies to key operations:
-
-```go
-key := cfg.Section("").Key("key name")
-```
-
-To check if a key exists:
-
-```go
-yes := cfg.Section("").HasKey("key name")
-```
-
-To create a new key:
-
-```go
-err := cfg.Section("").NewKey("name", "value")
-```
-
-To get a list of keys or key names:
-
-```go
-keys := cfg.Section("").Keys()
-names := cfg.Section("").KeyStrings()
-```
-
-To get a clone hash of keys and corresponding values:
-
-```go
-hash := cfg.Section("").KeysHash()
-```
-
-### Working with values
-
-To get a string value:
-
-```go
-val := cfg.Section("").Key("key name").String()
-```
-
-To validate key value on the fly:
-
-```go
-val := cfg.Section("").Key("key name").Validate(func(in string) string {
-	if len(in) == 0 {
-		return "default"
-	}
-	return in
-})
-```
-
-If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
-
-```go
-val := cfg.Section("").Key("key name").Value()
-```
-
-To check if raw value exists:
-
-```go
-yes := cfg.Section("").HasValue("test value")
-```
-
-To get value with types:
-
-```go
-// For boolean values:
-// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
-// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
-v, err = cfg.Section("").Key("BOOL").Bool()
-v, err = cfg.Section("").Key("FLOAT64").Float64()
-v, err = cfg.Section("").Key("INT").Int()
-v, err = cfg.Section("").Key("INT64").Int64()
-v, err = cfg.Section("").Key("UINT").Uint()
-v, err = cfg.Section("").Key("UINT64").Uint64()
-v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
-v, err = cfg.Section("").Key("TIME").Time() // RFC3339
-
-v = cfg.Section("").Key("BOOL").MustBool()
-v = cfg.Section("").Key("FLOAT64").MustFloat64()
-v = cfg.Section("").Key("INT").MustInt()
-v = cfg.Section("").Key("INT64").MustInt64()
-v = cfg.Section("").Key("UINT").MustUint()
-v = cfg.Section("").Key("UINT64").MustUint64()
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
-v = cfg.Section("").Key("TIME").MustTime() // RFC3339
-
-// Methods start with Must also accept one argument for default value
-// when key not found or fail to parse value to given type.
-// Except method MustString, which you have to pass a default value.
-
-v = cfg.Section("").Key("String").MustString("default")
-v = cfg.Section("").Key("BOOL").MustBool(true)
-v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
-v = cfg.Section("").Key("INT").MustInt(10)
-v = cfg.Section("").Key("INT64").MustInt64(99)
-v = cfg.Section("").Key("UINT").MustUint(3)
-v = cfg.Section("").Key("UINT64").MustUint64(6)
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
-v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
-```
-
-What if my value is three-line long?
-
-```ini
-[advance]
-ADDRESS = """404 road,
-NotFound, State, 5000
-Earth"""
-```
-
-Not a problem!
-
-```go
-cfg.Section("advance").Key("ADDRESS").String()
-
-/* --- start ---
-404 road,
-NotFound, State, 5000
-Earth
-------  end  --- */
-```
-
-That's cool, how about continuation lines?
-
-```ini
-[advance]
-two_lines = how about \
-	continuation lines?
-lots_of_lines = 1 \
-	2 \
-	3 \
-	4
-```
-
-Piece of cake!
-
-```go
-cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
-cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
-```
-
-Well, I hate continuation lines, how do I disable that?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{
-	IgnoreContinuation: true,
-}, "filename")
-```
-
-Holy crap! 
-
-Note that single quotes around values will be stripped:
-
-```ini
-foo = "some value" // foo: some value
-bar = 'some value' // bar: some value
-```
-
-Sometimes you downloaded file from [Crowdin](https://crowdin.com/) has values like the following (value is surrounded by double quotes and quotes in the value are escaped):
-
-```ini
-create_repo="created repository <a href=\"%s\">%s</a>"
-```
-
-How do you transform this to regular format automatically?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{UnescapeValueDoubleQuotes: true}, "en-US.ini"))
-cfg.Section("<name of your section>").Key("create_repo").String() 
-// You got: created repository <a href="%s">%s</a>
-```
-
-That's all? Hmm, no.
-
-#### Helper methods of working with values
-
-To get value with given candidates:
-
-```go
-v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
-v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
-v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
-v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
-v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
-v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
-```
-
-Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
-
-To validate value in a given range:
-
-```go
-vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
-vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
-vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
-vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
-vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
-vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
-vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
-```
-
-##### Auto-split values into a slice
-
-To use zero value of type for invalid inputs:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
-vals = cfg.Section("").Key("STRINGS").Strings(",")
-vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
-vals = cfg.Section("").Key("INTS").Ints(",")
-vals = cfg.Section("").Key("INT64S").Int64s(",")
-vals = cfg.Section("").Key("UINTS").Uints(",")
-vals = cfg.Section("").Key("UINT64S").Uint64s(",")
-vals = cfg.Section("").Key("TIMES").Times(",")
-```
-
-To exclude invalid values out of result slice:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [2.2]
-vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
-vals = cfg.Section("").Key("INTS").ValidInts(",")
-vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
-vals = cfg.Section("").Key("UINTS").ValidUints(",")
-vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
-vals = cfg.Section("").Key("TIMES").ValidTimes(",")
-```
-
-Or to return nothing but error when have invalid inputs:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> error
-vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
-vals = cfg.Section("").Key("INTS").StrictInts(",")
-vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
-vals = cfg.Section("").Key("UINTS").StrictUints(",")
-vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
-vals = cfg.Section("").Key("TIMES").StrictTimes(",")
-```
-
-### Save your configuration
-
-Finally, it's time to save your configuration to somewhere.
-
-A typical way to save configuration is writing it to a file:
-
-```go
-// ...
-err = cfg.SaveTo("my.ini")
-err = cfg.SaveToIndent("my.ini", "\t")
-```
-
-Another way to save is writing to a `io.Writer` interface:
-
-```go
-// ...
-cfg.WriteTo(writer)
-cfg.WriteToIndent(writer, "\t")
-```
-
-By default, spaces are used to align "=" sign between key and values, to disable that:
-
-```go
-ini.PrettyFormat = false
-``` 
-
-## Advanced Usage
-
-### Recursive Values
-
-For all value of keys, there is a special syntax `%(<name>)s`, where `<name>` is the key name in same section or default section, and `%(<name>)s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
-
-```ini
-NAME = ini
-
-[author]
-NAME = Unknwon
-GITHUB = https://github.com/%(NAME)s
-
-[package]
-FULL_NAME = github.com/go-ini/%(NAME)s
-```
-
-```go
-cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
-cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
-```
-
-### Parent-child Sections
-
-You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
-
-```ini
-NAME = ini
-VERSION = v1
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-```
-
-```go
-cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
-```
-
-#### Retrieve parent keys available to a child section
-
-```go
-cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
-```
-
-### Unparseable Sections
-
-Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
-
-body := cfg.Section("COMMENTS").Body()
-
-/* --- start ---
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
-------  end  --- */
-```
-
-### Auto-increment Key Names
-
-If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
-
-```ini
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-```
-
-```go
-cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
-```
+The minimum requirement of Go is **1.6**.
 
-### Map To Struct
-
-Want more objective way to play with INI? Cool.
-
-```ini
-Name = Unknwon
-age = 21
-Male = true
-Born = 1993-01-01T20:17:05Z
-
-[Note]
-Content = Hi is a good man!
-Cities = HangZhou, Boston
-```
-
-```go
-type Note struct {
-	Content string
-	Cities  []string
-}
-
-type Person struct {
-	Name string
-	Age  int `ini:"age"`
-	Male bool
-	Born time.Time
-	Note
-	Created time.Time `ini:"-"`
-}
-
-func main() {
-	cfg, err := ini.Load("path/to/ini")
-	// ...
-	p := new(Person)
-	err = cfg.MapTo(p)
-	// ...
-
-	// Things can be simpler.
-	err = ini.MapTo(p, "path/to/ini")
-	// ...
-
-	// Just map a section? Fine.
-	n := new(Note)
-	err = cfg.Section("Note").MapTo(n)
-	// ...
-}
-```
-
-Can I have default value for field? Absolutely.
-
-Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
-
-```go
-// ...
-p := &Person{
-	Name: "Joe",
-}
-// ...
-```
-
-It's really cool, but what's the point if you can't give me my file back from struct?
-
-### Reflect From Struct
-
-Why not?
-
-```go
-type Embeded struct {
-	Dates  []time.Time `delim:"|" comment:"Time data"`
-	Places []string    `ini:"places,omitempty"`
-	None   []int       `ini:",omitempty"`
-}
-
-type Author struct {
-	Name      string `ini:"NAME"`
-	Male      bool
-	Age       int `comment:"Author's age"`
-	GPA       float64
-	NeverMind string `ini:"-"`
-	*Embeded `comment:"Embeded section"`
-}
-
-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
-; Author's age
-Age = 21
-GPA = 2.8
-
-; Embeded section
-[Embeded]
-; Time data
-Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
-places = HangZhou,Boston
-```
-
-#### Name Mapper
-
-To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
-
-There are 2 built-in name mappers:
-
-- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
-- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
-
-To use them:
-
-```go
-type Info struct {
-	PackageName string
-}
-
-func main() {
-	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
-	// ...
-
-	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
-	// ...
-	info := new(Info)
-	cfg.NameMapper = ini.AllCapsUnderscore
-	err = cfg.MapTo(info)
-	// ...
-}
-```
-
-Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
-
-#### Value Mapper
-
-To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
-
-```go
-type Env struct {
-	Foo string `ini:"foo"`
-}
-
-func main() {
-	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
-	cfg.ValueMapper = os.ExpandEnv
-	// ...
-	env := &Env{}
-	err = cfg.Section("env").MapTo(env)
-}
-```
-
-This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
-
-#### Other Notes On Map/Reflect
-
-Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child
-}
-
-type Config struct {
-	City string
-	Parent
-}
-```
-
-Example configuration:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
+To use a tagged revision:
 
-[Child]
-Age = 21
+```sh
+$ go get gopkg.in/ini.v1
 ```
 
-What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child `ini:"Parent"`
-}
+To use with latest changes:
 
-type Config struct {
-	City string
-	Parent
-}
+```sh
+$ go get github.com/go-ini/ini
 ```
 
-Example configuration:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-Age = 21
-```
+Please add `-u` flag to update in the future.
 
 ## Getting Help
 
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
 - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
-- [File An Issue](https://github.com/go-ini/ini/issues/new)
-
-## FAQs
-
-### What does `BlockMode` field do?
-
-By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
-
-### Why another INI library?
-
-Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
-
-To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
 
 ## License
 
diff --git a/README_ZH.md b/README_ZH.md
deleted file mode 100644
index 69aefef..0000000
--- a/README_ZH.md
+++ /dev/null
@@ -1,750 +0,0 @@
-本包提供了 Go 语言中读写 INI 文件的功能。
-
-## 功能特性
-
-- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
-- 支持递归读取键值
-- 支持读取父子分区
-- 支持读取自增键名
-- 支持读取多行的键值
-- 支持大量辅助方法
-- 支持在读取时直接转换为 Go 语言类型
-- 支持读取和 **写入** 分区和键的注释
-- 轻松操作分区、键值和注释
-- 在保存文件时分区和键值会保持原有的顺序
-
-## 下载安装
-
-使用一个特定版本:
-
-    go get gopkg.in/ini.v1
-
-使用最新版:
-
-	go get github.com/go-ini/ini
-
-如需更新请添加 `-u` 选项。
-
-### 测试安装
-
-如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
-
-	go get -t gopkg.in/ini.v1
-
-如需更新请添加 `-u` 选项。
-
-## 开始使用
-
-### 从数据源加载
-
-一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
-
-```go
-cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
-```
-
-或者从一个空白的文件开始:
-
-```go
-cfg := ini.Empty()
-```
-
-当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
-
-```go
-err := cfg.Append("other file", []byte("other raw data"))
-```
-
-当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
-
-```go
-cfg, err := ini.LooseLoad("filename", "filename_404")
-```
-
-更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
-
-#### 忽略键名的大小写
-
-有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
-
-```go
-cfg, err := ini.InsensitiveLoad("filename")
-//...
-
-// sec1 和 sec2 指向同一个分区对象
-sec1, err := cfg.GetSection("Section")
-sec2, err := cfg.GetSection("SecTIOn")
-
-// key1 和 key2 指向同一个键对象
-key1, err := sec1.GetKey("Key")
-key2, err := sec2.GetKey("KeY")
-```
-
-#### 类似 MySQL 配置中的布尔值键
-
-MySQL 的配置文件中会出现没有具体值的布尔类型的键:
-
-```ini
-[mysqld]
-...
-skip-host-cache
-skip-name-resolve
-```
-
-默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
-```
-
-这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
-
-如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
-
-```go
-key, err := sec.NewBooleanKey("skip-host-cache")
-```
-
-#### 关于注释
-
-下述几种情况的内容将被视为注释:
-
-1. 所有以 `#` 或 `;` 开头的行
-2. 所有在 `#` 或 `;` 之后的内容
-3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
-
-如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
-
-除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, "app.ini"))
-```
-
-### 操作分区(Section)
-
-获取指定分区:
-
-```go
-section, err := cfg.GetSection("section name")
-```
-
-如果您想要获取默认分区,则可以用空字符串代替分区名:
-
-```go
-section, err := cfg.GetSection("")
-```
-
-当您非常确定某个分区是存在的,可以使用以下简便方法:
-
-```go
-section := cfg.Section("section name")
-```
-
-如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
-
-创建一个分区:
-
-```go
-err := cfg.NewSection("new section")
-```
-
-获取所有分区对象或名称:
-
-```go
-sections := cfg.Sections()
-names := cfg.SectionStrings()
-```
-
-### 操作键(Key)
-
-获取某个分区下的键:
-
-```go
-key, err := cfg.Section("").GetKey("key name")
-```
-
-和分区一样,您也可以直接获取键而忽略错误处理:
-
-```go
-key := cfg.Section("").Key("key name")
-```
-
-判断某个键是否存在:
-
-```go
-yes := cfg.Section("").HasKey("key name")
-```
-
-创建一个新的键:
-
-```go
-err := cfg.Section("").NewKey("name", "value")
-```
-
-获取分区下的所有键或键名:
-
-```go
-keys := cfg.Section("").Keys()
-names := cfg.Section("").KeyStrings()
-```
-
-获取分区下的所有键值对的克隆:
-
-```go
-hash := cfg.Section("").KeysHash()
-```
-
-### 操作键值(Value)
-
-获取一个类型为字符串(string)的值:
-
-```go
-val := cfg.Section("").Key("key name").String()
-```
-
-获取值的同时通过自定义函数进行处理验证:
-
-```go
-val := cfg.Section("").Key("key name").Validate(func(in string) string {
-	if len(in) == 0 {
-		return "default"
-	}
-	return in
-})
-```
-
-如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
-
-```go
-val := cfg.Section("").Key("key name").Value()
-```
-
-判断某个原值是否存在:
-
-```go
-yes := cfg.Section("").HasValue("test value")
-```
-
-获取其它类型的值:
-
-```go
-// 布尔值的规则:
-// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
-// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
-v, err = cfg.Section("").Key("BOOL").Bool()
-v, err = cfg.Section("").Key("FLOAT64").Float64()
-v, err = cfg.Section("").Key("INT").Int()
-v, err = cfg.Section("").Key("INT64").Int64()
-v, err = cfg.Section("").Key("UINT").Uint()
-v, err = cfg.Section("").Key("UINT64").Uint64()
-v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
-v, err = cfg.Section("").Key("TIME").Time() // RFC3339
-
-v = cfg.Section("").Key("BOOL").MustBool()
-v = cfg.Section("").Key("FLOAT64").MustFloat64()
-v = cfg.Section("").Key("INT").MustInt()
-v = cfg.Section("").Key("INT64").MustInt64()
-v = cfg.Section("").Key("UINT").MustUint()
-v = cfg.Section("").Key("UINT64").MustUint64()
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
-v = cfg.Section("").Key("TIME").MustTime() // RFC3339
-
-// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
-// 当键不存在或者转换失败时,则会直接返回该默认值。
-// 但是,MustString 方法必须传递一个默认值。
-
-v = cfg.Seciont("").Key("String").MustString("default")
-v = cfg.Section("").Key("BOOL").MustBool(true)
-v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
-v = cfg.Section("").Key("INT").MustInt(10)
-v = cfg.Section("").Key("INT64").MustInt64(99)
-v = cfg.Section("").Key("UINT").MustUint(3)
-v = cfg.Section("").Key("UINT64").MustUint64(6)
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
-v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
-```
-
-如果我的值有好多行怎么办?
-
-```ini
-[advance]
-ADDRESS = """404 road,
-NotFound, State, 5000
-Earth"""
-```
-
-嗯哼?小 case!
-
-```go
-cfg.Section("advance").Key("ADDRESS").String()
-
-/* --- start ---
-404 road,
-NotFound, State, 5000
-Earth
-------  end  --- */
-```
-
-赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
-
-```ini
-[advance]
-two_lines = how about \
-	continuation lines?
-lots_of_lines = 1 \
-	2 \
-	3 \
-	4
-```
-
-简直是小菜一碟!
-
-```go
-cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
-cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
-```
-
-可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{
-	IgnoreContinuation: true,
-}, "filename")
-```
-
-哇靠给力啊!
-
-需要注意的是,值两侧的单引号会被自动剔除:
-
-```ini
-foo = "some value" // foo: some value
-bar = 'some value' // bar: some value
-```
-
-有时您会获得像从 [Crowdin](https://crowdin.com/) 网站下载的文件那样具有特殊格式的值(值使用双引号括起来,内部的双引号被转义):
-
-```ini
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"
-```
-
-那么,怎么自动地将这类值进行处理呢?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{UnescapeValueDoubleQuotes: true}, "en-US.ini"))
-cfg.Section("<name of your section>").Key("create_repo").String() 
-// You got: 创建了仓库 <a href="%s">%s</a>
-```
-
-这就是全部了?哈哈,当然不是。
-
-#### 操作键值的辅助方法
-
-获取键值时设定候选值:
-
-```go
-v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
-v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
-v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
-v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
-v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
-v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
-```
-
-如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
-
-验证获取的值是否在指定范围内:
-
-```go
-vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
-vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
-vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
-vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
-vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
-vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
-vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
-```
-
-##### 自动分割键值到切片(slice)
-
-当存在无效输入时,使用零值代替:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
-vals = cfg.Section("").Key("STRINGS").Strings(",")
-vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
-vals = cfg.Section("").Key("INTS").Ints(",")
-vals = cfg.Section("").Key("INT64S").Int64s(",")
-vals = cfg.Section("").Key("UINTS").Uints(",")
-vals = cfg.Section("").Key("UINT64S").Uint64s(",")
-vals = cfg.Section("").Key("TIMES").Times(",")
-```
-
-从结果切片中剔除无效输入:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [2.2]
-vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
-vals = cfg.Section("").Key("INTS").ValidInts(",")
-vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
-vals = cfg.Section("").Key("UINTS").ValidUints(",")
-vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
-vals = cfg.Section("").Key("TIMES").ValidTimes(",")
-```
-
-当存在无效输入时,直接返回错误:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> error
-vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
-vals = cfg.Section("").Key("INTS").StrictInts(",")
-vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
-vals = cfg.Section("").Key("UINTS").StrictUints(",")
-vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
-vals = cfg.Section("").Key("TIMES").StrictTimes(",")
-```
-
-### 保存配置
-
-终于到了这个时刻,是时候保存一下配置了。
-
-比较原始的做法是输出配置到某个文件:
-
-```go
-// ...
-err = cfg.SaveTo("my.ini")
-err = cfg.SaveToIndent("my.ini", "\t")
-```
-
-另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
-
-```go
-// ...
-cfg.WriteTo(writer)
-cfg.WriteToIndent(writer, "\t")
-```
-
-默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
-
-```go
-ini.PrettyFormat = false
-``` 
-
-## 高级用法
-
-### 递归读取键值
-
-在获取所有键值的过程中,特殊语法 `%(<name>)s` 会被应用,其中 `<name>` 可以是相同分区或者默认分区下的键名。字符串 `%(<name>)s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
-
-```ini
-NAME = ini
-
-[author]
-NAME = Unknwon
-GITHUB = https://github.com/%(NAME)s
-
-[package]
-FULL_NAME = github.com/go-ini/%(NAME)s
-```
-
-```go
-cfg.Section("author").Key("GITHUB").String()		// https://github.com/Unknwon
-cfg.Section("package").Key("FULL_NAME").String()	// github.com/go-ini/ini
-```
-
-### 读取父子分区
-
-您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
-
-```ini
-NAME = ini
-VERSION = v1
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-```
-
-```go
-cfg.Section("package.sub").Key("CLONE_URL").String()	// https://gopkg.in/ini.v1
-```
-
-#### 获取上级父分区下的所有键名
-
-```go
-cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
-```
-
-### 无法解析的分区
-
-如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
-
-```go
-cfg, err := LoadSources(ini.LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
-
-body := cfg.Section("COMMENTS").Body()
-
-/* --- start ---
-<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
-------  end  --- */
-```
-
-### 读取自增键名
-
-如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
-
-```ini
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-```
-
-```go
-cfg.Section("features").KeyStrings()	// []{"#1", "#2", "#3"}
-```
-
-### 映射到结构
-
-想要使用更加面向对象的方式玩转 INI 吗?好主意。
-
-```ini
-Name = Unknwon
-age = 21
-Male = true
-Born = 1993-01-01T20:17:05Z
-
-[Note]
-Content = Hi is a good man!
-Cities = HangZhou, Boston
-```
-
-```go
-type Note struct {
-	Content string
-	Cities  []string
-}
-
-type Person struct {
-	Name string
-	Age  int `ini:"age"`
-	Male bool
-	Born time.Time
-	Note
-	Created time.Time `ini:"-"`
-}
-
-func main() {
-	cfg, err := ini.Load("path/to/ini")
-	// ...
-	p := new(Person)
-	err = cfg.MapTo(p)
-	// ...
-
-	// 一切竟可以如此的简单。
-	err = ini.MapTo(p, "path/to/ini")
-	// ...
-
-	// 嗯哼?只需要映射一个分区吗?
-	n := new(Note)
-	err = cfg.Section("Note").MapTo(n)
-	// ...
-}
-```
-
-结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
-
-```go
-// ...
-p := &Person{
-	Name: "Joe",
-}
-// ...
-```
-
-这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
-
-### 从结构反射
-
-可是,我有说不能吗?
-
-```go
-type Embeded struct {
-	Dates  []time.Time `delim:"|" comment:"Time data"`
-	Places []string    `ini:"places,omitempty"`
-	None   []int       `ini:",omitempty"`
-}
-
-type Author struct {
-	Name      string `ini:"NAME"`
-	Male      bool
-	Age       int `comment:"Author's age"`
-	GPA       float64
-	NeverMind string `ini:"-"`
-	*Embeded `comment:"Embeded section"`
-}
-
-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
-; Author's age
-Age = 21
-GPA = 2.8
-
-; Embeded section
-[Embeded]
-; Time data
-Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
-places = HangZhou,Boston
-```
-
-#### 名称映射器(Name Mapper)
-
-为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
-
-目前有 2 款内置的映射器:
-
-- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
-- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
-
-使用方法:
-
-```go
-type Info struct{
-	PackageName string
-}
-
-func main() {
-	err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
-	// ...
-
-	cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
-	// ...
-	info := new(Info)
-	cfg.NameMapper = ini.AllCapsUnderscore
-	err = cfg.MapTo(info)
-	// ...
-}
-```
-
-使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
-
-#### 值映射器(Value Mapper)
-
-值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
-
-```go
-type Env struct {
-	Foo string `ini:"foo"`
-}
-
-func main() {
-	cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
-	cfg.ValueMapper = os.ExpandEnv
-	// ...
-	env := &Env{}
-	err = cfg.Section("env").MapTo(env)
-}
-```
-
-本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
-
-#### 映射/反射的其它说明
-
-任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child
-}
-
-type Config struct {
-	City string
-	Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-
-[Child]
-Age = 21
-```
-
-很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
-
-```go
-type Child struct {
-	Age string
-}
-
-type Parent struct {
-	Name string
-	Child `ini:"Parent"`
-}
-
-type Config struct {
-	City string
-	Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-Age = 21
-```
-
-## 获取帮助
-
-- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
-- [创建工单](https://github.com/go-ini/ini/issues/new)
-
-## 常见问题
-
-### 字段 `BlockMode` 是什么?
-
-默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
-
-### 为什么要写另一个 INI 解析库?
-
-许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
-
-为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
diff --git a/debian/changelog b/debian/changelog
index 3afb0a8..69cb134 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-ini-ini (1.42.0-1) UNRELEASED; urgency=medium
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 05 May 2019 01:06:01 +0000
+
 golang-github-go-ini-ini (1.32.0-2) unstable; urgency=medium
 
   * Fix URI of Vcs-Browser and Vcs-Git.
diff --git a/file.go b/file.go
index ce26c3b..0ed0eaf 100644
--- a/file.go
+++ b/file.go
@@ -45,6 +45,9 @@ type File struct {
 
 // newFile initializes File object with given data sources.
 func newFile(dataSources []dataSource, opts LoadOptions) *File {
+	if len(opts.KeyValueDelimiters) == 0 {
+		opts.KeyValueDelimiters = "=:"
+	}
 	return &File{
 		BlockMode:   true,
 		dataSources: dataSources,
@@ -140,9 +143,14 @@ func (f *File) Section(name string) *Section {
 
 // Section returns list of Section.
 func (f *File) Sections() []*Section {
+	if f.BlockMode {
+		f.lock.RLock()
+		defer f.lock.RUnlock()
+	}
+
 	sections := make([]*Section, len(f.sectionList))
-	for i := range f.sectionList {
-		sections[i] = f.Section(f.sectionList[i])
+	for i, name := range f.sectionList {
+		sections[i] = f.sections[name]
 	}
 	return sections
 }
@@ -222,8 +230,9 @@ func (f *File) Append(source interface{}, others ...interface{}) error {
 }
 
 func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
-	equalSign := "="
-	if PrettyFormat {
+	equalSign := DefaultFormatLeft + "=" + DefaultFormatRight
+
+	if PrettyFormat || PrettyEqual {
 		equalSign = " = "
 	}
 
@@ -232,13 +241,18 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
 	for i, sname := range f.sectionList {
 		sec := f.Section(sname)
 		if len(sec.Comment) > 0 {
-			if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
-				sec.Comment = "; " + sec.Comment
-			} else {
-				sec.Comment = sec.Comment[:1] + " " + strings.TrimSpace(sec.Comment[1:])
-			}
-			if _, err := buf.WriteString(sec.Comment + LineBreak); err != nil {
-				return nil, err
+			// Support multiline comments
+			lines := strings.Split(sec.Comment, LineBreak)
+			for i := range lines {
+				if lines[i][0] != '#' && lines[i][0] != ';' {
+					lines[i] = "; " + lines[i]
+				} else {
+					lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+				}
+
+				if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+					return nil, err
+				}
 			}
 		}
 
@@ -275,7 +289,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
 			for _, kname := range sec.keyList {
 				keyLength := len(kname)
 				// First case will surround key by ` and second by """
-				if strings.ContainsAny(kname, "\"=:") {
+				if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
 					keyLength += 2
 				} else if strings.Contains(kname, "`") {
 					keyLength += 6
@@ -295,13 +309,19 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
 				if len(indent) > 0 && sname != DEFAULT_SECTION {
 					buf.WriteString(indent)
 				}
-				if key.Comment[0] != '#' && key.Comment[0] != ';' {
-					key.Comment = "; " + key.Comment
-				} else {
-					key.Comment = key.Comment[:1] + " " + strings.TrimSpace(key.Comment[1:])
-				}
-				if _, err := buf.WriteString(key.Comment + LineBreak); err != nil {
-					return nil, err
+
+				// Support multiline comments
+				lines := strings.Split(key.Comment, LineBreak)
+				for i := range lines {
+					if lines[i][0] != '#' && lines[i][0] != ';' {
+						lines[i] = "; " + strings.TrimSpace(lines[i])
+					} else {
+						lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+					}
+
+					if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+						return nil, err
+					}
 				}
 			}
 
@@ -312,7 +332,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
 			switch {
 			case key.isAutoIncrement:
 				kname = "-"
-			case strings.ContainsAny(kname, "\"=:"):
+			case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
 				kname = "`" + kname + "`"
 			case strings.Contains(kname, "`"):
 				kname = `"""` + kname + `"""`
diff --git a/file_test.go b/file_test.go
index 11612eb..2298c1e 100644
--- a/file_test.go
+++ b/file_test.go
@@ -16,6 +16,7 @@ package ini_test
 
 import (
 	"bytes"
+	"io/ioutil"
 	"testing"
 
 	. "github.com/smartystreets/goconvey/convey"
@@ -253,93 +254,44 @@ func TestFile_WriteTo(t *testing.T) {
 		var buf bytes.Buffer
 		_, err = f.WriteTo(&buf)
 		So(err, ShouldBeNil)
-		So(buf.String(), ShouldEqual, `; 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]
-; This is author name
-NAME   = Unknwon
-E-MAIL = u@gogs.io
-GITHUB = https://github.com/%(NAME)s
-# Succeeding comment
-BIO    = """Gopher.
-Coding addict.
-Good man.
-"""
-
-[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
-DURATION   = 2h45m
-UINT       = 3
-
-[array]
-STRINGS  = en, zh, de
-FLOAT64S = 1.1, 2.2, 3.3
-INTS     = 1, 2, 3
-UINTS    = 1, 2, 3
-TIMES    = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
-
-[note]
-empty_lines = next line is empty
-boolean_key
-more        = notes
-
-; Comment before the section
-; This is a comment for the section too
-[comments]
-; Comment before key
-key  = value
-; This is a comment for key2
-key2 = value2
-key3 = "one", "two", "three"
-
-[string escapes]
-key1 = value1, value2, value3
-key2 = value1\, value2
-key3 = val\ue1, value2
-key4 = value1\\, value\\\\2
-key5 = value1\,, value2
-key6 = aaa bbb\ and\ space ccc
-
-[advance]
-value with quotes      = some value
-value quote2 again     = some value
-includes comment sign  = `+"`"+"my#password"+"`"+`
-includes comment sign2 = `+"`"+"my;password"+"`"+`
-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"""
-two_lines              = how about continuation lines?
-lots_of_lines          = 1 2 3 4 
+
+		golden := "testdata/TestFile_WriteTo.golden"
+		if *update {
+			ioutil.WriteFile(golden, buf.Bytes(), 0644)
+		}
+
+		expected, err := ioutil.ReadFile(golden)
+		So(err, ShouldBeNil)
+		So(buf.String(), ShouldEqual, string(expected))
+	})
+
+	Convey("Support multiline comments", t, func() {
+		f, err := ini.Load([]byte(`
+# 
+# general.domain
+# 
+# Domain name of XX system.
+domain      = mydomain.com
+`))
+		So(err, ShouldBeNil)
+
+		f.Section("").Key("test").Comment = "Multiline\nComment"
+
+		var buf bytes.Buffer
+		_, err = f.WriteTo(&buf)
+		So(err, ShouldBeNil)
+
+		So(buf.String(), ShouldEqual, `# 
+# general.domain
+# 
+# Domain name of XX system.
+domain = mydomain.com
+; Multiline
+; Comment
+test   = 
 
 `)
+
 	})
 }
 
diff --git a/ini.go b/ini.go
index 535d358..f827a1e 100644
--- a/ini.go
+++ b/ini.go
@@ -1,3 +1,5 @@
+// +build go1.6
+
 // Copyright 2014 Unknwon
 //
 // Licensed under the Apache License, Version 2.0 (the "License"): you may
@@ -32,7 +34,7 @@ const (
 
 	// Maximum allowed depth when recursively substituing variable names.
 	_DEPTH_VALUES = 99
-	_VERSION      = "1.32.0"
+	_VERSION      = "1.42.0"
 )
 
 // Version returns current package version literal.
@@ -46,6 +48,10 @@ var (
 	// at package init time.
 	LineBreak = "\n"
 
+	// Place custom spaces when PrettyFormat and PrettyEqual are both disabled
+	DefaultFormatLeft  = ""
+	DefaultFormatRight = ""
+
 	// Variable regexp pattern: %(variable)s
 	varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
 
@@ -53,6 +59,9 @@ var (
 	// or reduce all possible spaces for compact format.
 	PrettyFormat = true
 
+	// Place spaces around "=" sign even when PrettyFormat is false
+	PrettyEqual = false
+
 	// Explicitly write DEFAULT section header
 	DefaultHeader = false
 
@@ -129,6 +138,8 @@ type LoadOptions struct {
 	IgnoreContinuation bool
 	// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
 	IgnoreInlineComment bool
+	// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+	SkipUnrecognizableLines 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
@@ -137,6 +148,16 @@ type LoadOptions struct {
 	// AllowNestedValues indicates whether to allow AWS-like nested values.
 	// Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
 	AllowNestedValues bool
+	// AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+	// Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+	// Relevant quote:  Values can also span multiple lines, as long as they are indented deeper
+	// than the first line of the value.
+	AllowPythonMultilineValues bool
+	// SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+	// Docs: https://docs.python.org/2/library/configparser.html
+	// Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+	// In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+	SpaceBeforeInlineComment bool
 	// UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
 	// when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
 	UnescapeValueDoubleQuotes bool
@@ -144,9 +165,13 @@ type LoadOptions struct {
 	// when value is NOT surrounded by any quotes.
 	// Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
 	UnescapeValueCommentSymbols bool
-	// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
+	// UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
 	// conform to key/value pairs. Specify the names of those blocks here.
 	UnparseableSections []string
+	// KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+	KeyValueDelimiters string
+	// PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+	PreserveSurroundedQuote bool
 }
 
 func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
@@ -187,7 +212,7 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
 	return LoadSources(LoadOptions{Insensitive: true}, source, others...)
 }
 
-// InsensitiveLoad has exactly same functionality as Load function
+// ShadowLoad has exactly same functionality as Load function
 // except it allows have shadow keys.
 func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
 	return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
diff --git a/ini_test.go b/ini_test.go
index 3e6992d..7ec7038 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -16,6 +16,7 @@ package ini_test
 
 import (
 	"bytes"
+	"flag"
 	"io/ioutil"
 	"testing"
 
@@ -47,6 +48,8 @@ const (
 	_NOT_FOUND_CONF = "testdata/404.ini"
 )
 
+var update = flag.Bool("update", false, "Update .golden files")
+
 func TestLoad(t *testing.T) {
 	Convey("Load from good data sources", t, func() {
 		f, err := ini.Load([]byte(`
@@ -62,7 +65,7 @@ NAME = Unknwon
 		So(err, ShouldBeNil)
 		So(f, ShouldNotBeNil)
 
-		// Vaildate values make sure all sources are loaded correctly
+		// Validate values make sure all sources are loaded correctly
 		sec := f.Section("")
 		So(sec.Key("NAME").String(), ShouldEqual, "ini")
 		So(sec.Key("VERSION").String(), ShouldEqual, "v1")
@@ -84,208 +87,1063 @@ NAME = Unknwon
 			So(err, ShouldNotBeNil)
 		})
 	})
+
+	Convey("Can't properly parse INI files containing `#` or `;` in value", t, func() {
+		f, err := ini.Load([]byte(`
+	[author]
+	NAME = U#n#k#n#w#o#n
+	GITHUB = U;n;k;n;w;o;n
+	`))
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		sec := f.Section("author")
+		nameValue := sec.Key("NAME").String()
+		githubValue := sec.Key("GITHUB").String()
+		So(nameValue, ShouldEqual, "U")
+		So(githubValue, ShouldEqual, "U")
+	})
+
+	Convey("Can't parse small python-compatible INI files", t, func() {
+		f, err := ini.Load([]byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+   foo
+   bar
+   foobar
+   barfoo
+   -----END RSA PRIVATE KEY-----
+`))
+		So(err, ShouldNotBeNil)
+		So(f, ShouldBeNil)
+		So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
+	})
+
+	Convey("Can't parse big python-compatible INI files", t, func() {
+		f, err := ini.Load([]byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+   1foo
+   2bar
+   3foobar
+   4barfoo
+   5foo
+   6bar
+   7foobar
+   8barfoo
+   9foo
+   10bar
+   11foobar
+   12barfoo
+   13foo
+   14bar
+   15foobar
+   16barfoo
+   17foo
+   18bar
+   19foobar
+   20barfoo
+   21foo
+   22bar
+   23foobar
+   24barfoo
+   25foo
+   26bar
+   27foobar
+   28barfoo
+   29foo
+   30bar
+   31foobar
+   32barfoo
+   33foo
+   34bar
+   35foobar
+   36barfoo
+   37foo
+   38bar
+   39foobar
+   40barfoo
+   41foo
+   42bar
+   43foobar
+   44barfoo
+   45foo
+   46bar
+   47foobar
+   48barfoo
+   49foo
+   50bar
+   51foobar
+   52barfoo
+   53foo
+   54bar
+   55foobar
+   56barfoo
+   57foo
+   58bar
+   59foobar
+   60barfoo
+   61foo
+   62bar
+   63foobar
+   64barfoo
+   65foo
+   66bar
+   67foobar
+   68barfoo
+   69foo
+   70bar
+   71foobar
+   72barfoo
+   73foo
+   74bar
+   75foobar
+   76barfoo
+   77foo
+   78bar
+   79foobar
+   80barfoo
+   81foo
+   82bar
+   83foobar
+   84barfoo
+   85foo
+   86bar
+   87foobar
+   88barfoo
+   89foo
+   90bar
+   91foobar
+   92barfoo
+   93foo
+   94bar
+   95foobar
+   96barfoo
+   -----END RSA PRIVATE KEY-----
+`))
+		So(err, ShouldNotBeNil)
+		So(f, ShouldBeNil)
+		So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
+	})
 }
 
-func TestLoadSources(t *testing.T) {
-	Convey("Load from data sources with options", t, func() {
-		Convey("Ignore nonexistent files", func() {
-			f, err := ini.LooseLoad(_NOT_FOUND_CONF, _MINIMAL_CONF)
+func TestLooseLoad(t *testing.T) {
+	Convey("Load from data sources with option `Loose` true", t, func() {
+		f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		Convey("Inverse case", func() {
+			_, err = ini.Load(_NOT_FOUND_CONF)
+			So(err, ShouldNotBeNil)
+		})
+	})
+}
+
+func TestInsensitiveLoad(t *testing.T) {
+	Convey("Insensitive to section and key names", t, func() {
+		f, err := ini.InsensitiveLoad(_MINIMAL_CONF)
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
+
+		Convey("Write out", func() {
+			var buf bytes.Buffer
+			_, err := f.WriteTo(&buf)
 			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+			So(buf.String(), ShouldEqual, `[author]
+e-mail = u@gogs.io
 
-			Convey("Inverse case", func() {
-				_, err = ini.Load(_NOT_FOUND_CONF)
-				So(err, ShouldNotBeNil)
-			})
+`)
 		})
 
-		Convey("Insensitive to section and key names", func() {
-			f, err := ini.InsensitiveLoad(_MINIMAL_CONF)
+		Convey("Inverse case", func() {
+			f, err := ini.Load(_MINIMAL_CONF)
 			So(err, ShouldBeNil)
 			So(f, ShouldNotBeNil)
 
-			So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
+			So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
+		})
+	})
+}
 
-			Convey("Write out", func() {
-				var buf bytes.Buffer
-				_, err := f.WriteTo(&buf)
+func TestLoadSources(t *testing.T) {
+	Convey("Load from data sources with options", t, func() {
+		Convey("with true `AllowPythonMultilineValues`", func() {
+			Convey("Ignore nonexistent files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
 				So(err, ShouldBeNil)
-				So(buf.String(), ShouldEqual, `[author]
-e-mail = u@gogs.io
+				So(f, ShouldNotBeNil)
 
-`)
+				Convey("Inverse case", func() {
+					_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _NOT_FOUND_CONF)
+					So(err, ShouldNotBeNil)
+				})
 			})
 
-			Convey("Inverse case", func() {
-				f, err := ini.Load(_MINIMAL_CONF)
+			Convey("Insensitive to section and key names", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, _MINIMAL_CONF)
 				So(err, ShouldBeNil)
 				So(f, ShouldNotBeNil)
 
-				So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
+				So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
+
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `[author]
+e-mail = u@gogs.io
+
+`)
+				})
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, _MINIMAL_CONF)
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
+				})
 			})
-		})
 
-		Convey("Ignore continuation lines", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				IgnoreContinuation: true,
-			}, []byte(`
+			Convey("Ignore continuation lines", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					IgnoreContinuation:         true,
+				}, []byte(`
 key1=a\b\
 key2=c\d\
 key3=value`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
-			So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
-			So(f.Section("").Key("key3").String(), ShouldEqual, "value")
+				So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
+				So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
+				So(f.Section("").Key("key3").String(), ShouldEqual, "value")
 
-			Convey("Inverse case", func() {
-				f, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
 key1=a\b\
 key2=c\d\`))
-				So(err, ShouldBeNil)
-				So(f, ShouldNotBeNil)
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
 
-				So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
+					So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
+				})
 			})
-		})
 
-		Convey("Ignore inline comments", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				IgnoreInlineComment: true,
-			}, []byte(`
+			Convey("Ignore inline comments", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					IgnoreInlineComment:        true,
+				}, []byte(`
 key1=value ;comment
-key2=value2 #comment2`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+key2=value2 #comment2
+key3=val#ue #comment3`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
-			So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
+				So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
+				So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
+				So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
 
-			Convey("Inverse case", func() {
-				f, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
 key1=value ;comment
 key2=value2 #comment2`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
+					So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
+					So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
+					So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
+				})
+			})
+
+			Convey("Skip unrecognizable lines", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					SkipUnrecognizableLines: true,
+				}, []byte(`
+GenerationDepth: 13
+
+BiomeRarityScale: 100
+
+################
+# Biome Groups #
+################
+
+BiomeGroup(NormalBiomes, 3, 99, RoofedForestEnchanted, ForestSakura, FloatingJungle
+BiomeGroup(IceBiomes, 4, 85, Ice Plains)
+`))
 				So(err, ShouldBeNil)
 				So(f, ShouldNotBeNil)
 
-				So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
-				So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
-				So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
-				So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
+				So(f.Section("").Key("GenerationDepth").String(), ShouldEqual, "13")
+				So(f.Section("").Key("BiomeRarityScale").String(), ShouldEqual, "100")
+				So(f.Section("").HasKey("BiomeGroup"), ShouldBeFalse)
 			})
-		})
 
-		Convey("Allow boolean type keys", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				AllowBooleanKeys: true,
-			}, []byte(`
+			Convey("Allow boolean type keys", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					AllowBooleanKeys:           true,
+				}, []byte(`
 key1=hello
 #key2
 key3`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
-			So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
+				So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
+				So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
 
-			Convey("Write out", func() {
-				var buf bytes.Buffer
-				_, err := f.WriteTo(&buf)
-				So(err, ShouldBeNil)
-				So(buf.String(), ShouldEqual, `key1 = hello
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `key1 = hello
 # key2
 key3
 `)
-			})
+				})
 
-			Convey("Inverse case", func() {
-				_, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
 key1=hello
 #key2
 key3`))
-				So(err, ShouldNotBeNil)
+					So(err, ShouldNotBeNil)
+				})
 			})
-		})
 
-		Convey("Allow shadow keys", func() {
-			f, err := ini.ShadowLoad([]byte(`
+			Convey("Allow shadow keys", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true, AllowPythonMultilineValues: true}, []byte(`
 [remote "origin"]
 url = https://github.com/Antergone/test1.git
 url = https://github.com/Antergone/test2.git
 fetch = +refs/heads/*:refs/remotes/origin/*`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
-			So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
-				"https://github.com/Antergone/test1.git",
-				"https://github.com/Antergone/test2.git",
-			})
-			So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
+				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
+				So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
+					"https://github.com/Antergone/test1.git",
+					"https://github.com/Antergone/test2.git",
+				})
+				So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
 
-			Convey("Write out", func() {
-				var buf bytes.Buffer
-				_, err := f.WriteTo(&buf)
-				So(err, ShouldBeNil)
-				So(buf.String(), ShouldEqual, `[remote "origin"]
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `[remote "origin"]
 url   = https://github.com/Antergone/test1.git
 url   = https://github.com/Antergone/test2.git
 fetch = +refs/heads/*:refs/remotes/origin/*
 
 `)
-			})
+				})
 
-			Convey("Inverse case", func() {
-				f, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
 [remote "origin"]
 url = https://github.com/Antergone/test1.git
 url = https://github.com/Antergone/test2.git`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
+				})
+			})
+
+			Convey("Unescape double quotes inside value", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					UnescapeValueDoubleQuotes:  true,
+				}, []byte(`
+create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
+create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
+				})
+			})
+
+			Convey("Unescape comment symbols inside value", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues:  true,
+					IgnoreInlineComment:         true,
+					UnescapeValueCommentSymbols: true,
+				}, []byte(`
+key = test value <span style="color: %s\; background: %s">more text</span>
+`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
+			})
+
+			Convey("Can parse small python-compatible INI files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					Insensitive:                true,
+					UnparseableSections:        []string{"core_lesson", "comments"},
+				}, []byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+  foo
+  bar
+  foobar
+  barfoo
+  -----END RSA PRIVATE KEY-----
+`))
 				So(err, ShouldBeNil)
 				So(f, ShouldNotBeNil)
 
-				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
+				So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
+			})
+
+			Convey("Can parse big python-compatible INI files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					Insensitive:                true,
+					UnparseableSections:        []string{"core_lesson", "comments"},
+				}, []byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+   1foo
+   2bar
+   3foobar
+   4barfoo
+   5foo
+   6bar
+   7foobar
+   8barfoo
+   9foo
+   10bar
+   11foobar
+   12barfoo
+   13foo
+   14bar
+   15foobar
+   16barfoo
+   17foo
+   18bar
+   19foobar
+   20barfoo
+   21foo
+   22bar
+   23foobar
+   24barfoo
+   25foo
+   26bar
+   27foobar
+   28barfoo
+   29foo
+   30bar
+   31foobar
+   32barfoo
+   33foo
+   34bar
+   35foobar
+   36barfoo
+   37foo
+   38bar
+   39foobar
+   40barfoo
+   41foo
+   42bar
+   43foobar
+   44barfoo
+   45foo
+   46bar
+   47foobar
+   48barfoo
+   49foo
+   50bar
+   51foobar
+   52barfoo
+   53foo
+   54bar
+   55foobar
+   56barfoo
+   57foo
+   58bar
+   59foobar
+   60barfoo
+   61foo
+   62bar
+   63foobar
+   64barfoo
+   65foo
+   66bar
+   67foobar
+   68barfoo
+   69foo
+   70bar
+   71foobar
+   72barfoo
+   73foo
+   74bar
+   75foobar
+   76barfoo
+   77foo
+   78bar
+   79foobar
+   80barfoo
+   81foo
+   82bar
+   83foobar
+   84barfoo
+   85foo
+   86bar
+   87foobar
+   88barfoo
+   89foo
+   90bar
+   91foobar
+   92barfoo
+   93foo
+   94bar
+   95foobar
+   96barfoo
+   -----END RSA PRIVATE KEY-----
+`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, `-----BEGIN RSA PRIVATE KEY-----
+1foo
+2bar
+3foobar
+4barfoo
+5foo
+6bar
+7foobar
+8barfoo
+9foo
+10bar
+11foobar
+12barfoo
+13foo
+14bar
+15foobar
+16barfoo
+17foo
+18bar
+19foobar
+20barfoo
+21foo
+22bar
+23foobar
+24barfoo
+25foo
+26bar
+27foobar
+28barfoo
+29foo
+30bar
+31foobar
+32barfoo
+33foo
+34bar
+35foobar
+36barfoo
+37foo
+38bar
+39foobar
+40barfoo
+41foo
+42bar
+43foobar
+44barfoo
+45foo
+46bar
+47foobar
+48barfoo
+49foo
+50bar
+51foobar
+52barfoo
+53foo
+54bar
+55foobar
+56barfoo
+57foo
+58bar
+59foobar
+60barfoo
+61foo
+62bar
+63foobar
+64barfoo
+65foo
+66bar
+67foobar
+68barfoo
+69foo
+70bar
+71foobar
+72barfoo
+73foo
+74bar
+75foobar
+76barfoo
+77foo
+78bar
+79foobar
+80barfoo
+81foo
+82bar
+83foobar
+84barfoo
+85foo
+86bar
+87foobar
+88barfoo
+89foo
+90bar
+91foobar
+92barfoo
+93foo
+94bar
+95foobar
+96barfoo
+-----END RSA PRIVATE KEY-----`)
+			})
+
+			Convey("Allow unparsable sections", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: true,
+					Insensitive:                true,
+					UnparseableSections:        []string{"core_lesson", "comments"},
+				}, []byte(`
+Lesson_Location = 87
+Lesson_Status = C
+Score = 3
+Time = 00:02:30
+
+[CORE_LESSON]
+my lesson state data – 1111111111111111111000000000000000001110000
+111111111111111111100000000000111000000000 – end my lesson state data
+
+[COMMENTS]
+<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").Key("score").String(), ShouldEqual, "3")
+				So(f.Section("").Body(), ShouldBeEmpty)
+				So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
+111111111111111111100000000000111000000000 – end my lesson state data`)
+				So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
+
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `lesson_location = 87
+lesson_status   = C
+score           = 3
+time            = 00:02:30
+
+[core_lesson]
+my lesson state data – 1111111111111111111000000000000000001110000
+111111111111111111100000000000111000000000 – end my lesson state data
+
+[comments]
+<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
+`)
+				})
+
+				Convey("Inverse case", func() {
+					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(`
+[CORE_LESSON]
+my lesson state data – 1111111111111111111000000000000000001110000
+111111111111111111100000000000111000000000 – end my lesson state data`))
+					So(err, ShouldNotBeNil)
+				})
+			})
+
+			Convey("And false `SpaceBeforeInlineComment`", func() {
+				Convey("Can't parse INI files containing `#` or `;` in value", func() {
+					f, err := ini.LoadSources(
+						ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: false},
+						[]byte(`
+[author]
+NAME = U#n#k#n#w#o#n
+GITHUB = U;n;k;n;w;o;n
+`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+					sec := f.Section("author")
+					nameValue := sec.Key("NAME").String()
+					githubValue := sec.Key("GITHUB").String()
+					So(nameValue, ShouldEqual, "U")
+					So(githubValue, ShouldEqual, "U")
+				})
+			})
+
+			Convey("And true `SpaceBeforeInlineComment`", func() {
+				Convey("Can parse INI files containing `#` or `;` in value", func() {
+					f, err := ini.LoadSources(
+						ini.LoadOptions{AllowPythonMultilineValues: false, SpaceBeforeInlineComment: true},
+						[]byte(`
+[author]
+NAME = U#n#k#n#w#o#n
+GITHUB = U;n;k;n;w;o;n
+`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+					sec := f.Section("author")
+					nameValue := sec.Key("NAME").String()
+					githubValue := sec.Key("GITHUB").String()
+					So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
+					So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
+				})
 			})
 		})
 
-		Convey("Unescape double quotes inside value", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				UnescapeValueDoubleQuotes: true,
-			}, []byte(`
-create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+		Convey("with false `AllowPythonMultilineValues`", func() {
+			Convey("Ignore nonexistent files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Loose: true}, _NOT_FOUND_CONF, _MINIMAL_CONF)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				Convey("Inverse case", func() {
+					_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _NOT_FOUND_CONF)
+					So(err, ShouldNotBeNil)
+				})
+			})
+
+			Convey("Insensitive to section and key names", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, Insensitive: true}, _MINIMAL_CONF)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("Author").Key("e-mail").String(), ShouldEqual, "u@gogs.io")
+
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `[author]
+e-mail = u@gogs.io
+
+`)
+				})
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, _MINIMAL_CONF)
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
+				})
+			})
+
+			Convey("Ignore continuation lines", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: false,
+					IgnoreContinuation:         true,
+				}, []byte(`
+key1=a\b\
+key2=c\d\
+key3=value`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").Key("key1").String(), ShouldEqual, `a\b\`)
+				So(f.Section("").Key("key2").String(), ShouldEqual, `c\d\`)
+				So(f.Section("").Key("key3").String(), ShouldEqual, "value")
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+key1=a\b\
+key2=c\d\`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("").Key("key1").String(), ShouldEqual, `a\bkey2=c\d`)
+				})
+			})
+
+			Convey("Ignore inline comments", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: false,
+					IgnoreInlineComment:        true,
+				}, []byte(`
+key1=value ;comment
+key2=value2 #comment2
+key3=val#ue #comment3`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`)
+				So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`)
+				So(f.Section("").Key("key3").String(), ShouldEqual, `val#ue #comment3`)
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+key1=value ;comment
+key2=value2 #comment2`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("").Key("key1").String(), ShouldEqual, `value`)
+					So(f.Section("").Key("key1").Comment, ShouldEqual, `;comment`)
+					So(f.Section("").Key("key2").String(), ShouldEqual, `value2`)
+					So(f.Section("").Key("key2").Comment, ShouldEqual, `#comment2`)
+				})
+			})
+
+			Convey("Allow boolean type keys", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: false,
+					AllowBooleanKeys:           true,
+				}, []byte(`
+key1=hello
+#key2
+key3`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section("").KeyStrings(), ShouldResemble, []string{"key1", "key3"})
+				So(f.Section("").Key("key3").MustBool(false), ShouldBeTrue)
+
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `key1 = hello
+# key2
+key3
+`)
+				})
+
+				Convey("Inverse case", func() {
+					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+key1=hello
+#key2
+key3`))
+					So(err, ShouldNotBeNil)
+				})
+			})
+
+			Convey("Allow shadow keys", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false, AllowShadows: true}, []byte(`
+[remote "origin"]
+url = https://github.com/Antergone/test1.git
+url = https://github.com/Antergone/test2.git
+fetch = +refs/heads/*:refs/remotes/origin/*`))
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
+
+				So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git")
+				So(f.Section(`remote "origin"`).Key("url").ValueWithShadows(), ShouldResemble, []string{
+					"https://github.com/Antergone/test1.git",
+					"https://github.com/Antergone/test2.git",
+				})
+				So(f.Section(`remote "origin"`).Key("fetch").String(), ShouldEqual, "+refs/heads/*:refs/remotes/origin/*")
+
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `[remote "origin"]
+url   = https://github.com/Antergone/test1.git
+url   = https://github.com/Antergone/test2.git
+fetch = +refs/heads/*:refs/remotes/origin/*
 
-			So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
+`)
+				})
 
-			Convey("Inverse case", func() {
-				f, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+[remote "origin"]
+url = https://github.com/Antergone/test1.git
+url = https://github.com/Antergone/test2.git`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git")
+				})
+			})
+
+			Convey("Unescape double quotes inside value", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: false,
+					UnescapeValueDoubleQuotes:  true,
+				}, []byte(`
 create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
 				So(err, ShouldBeNil)
 				So(f, ShouldNotBeNil)
 
-				So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
+				So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 <a href="%s">%s</a>`)
+
+				Convey("Inverse case", func() {
+					f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+create_repo="创建了仓库 <a href=\"%s\">%s</a>"`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+
+					So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 <a href=\"%s\">%s</a>"`)
+				})
 			})
-		})
 
-		Convey("Unescape comment symbols inside value", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				IgnoreInlineComment:         true,
-				UnescapeValueCommentSymbols: true,
-			}, []byte(`
+			Convey("Unescape comment symbols inside value", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues:  false,
+					IgnoreInlineComment:         true,
+					UnescapeValueCommentSymbols: true,
+				}, []byte(`
 key = test value <span style="color: %s\; background: %s">more text</span>
 `))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
-		})
+				So(f.Section("").Key("key").String(), ShouldEqual, `test value <span style="color: %s; background: %s">more text</span>`)
+			})
+
+			Convey("Can't parse small python-compatible INI files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+  foo
+  bar
+  foobar
+  barfoo
+  -----END RSA PRIVATE KEY-----
+`))
+				So(err, ShouldNotBeNil)
+				So(f, ShouldBeNil)
+				So(err.Error(), ShouldEqual, "key-value delimiter not found: foo\n")
+			})
+
+			Convey("Can't parse big python-compatible INI files", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
+[long]
+long_rsa_private_key = -----BEGIN RSA PRIVATE KEY-----
+  1foo
+  2bar
+  3foobar
+  4barfoo
+  5foo
+  6bar
+  7foobar
+  8barfoo
+  9foo
+  10bar
+  11foobar
+  12barfoo
+  13foo
+  14bar
+  15foobar
+  16barfoo
+  17foo
+  18bar
+  19foobar
+  20barfoo
+  21foo
+  22bar
+  23foobar
+  24barfoo
+  25foo
+  26bar
+  27foobar
+  28barfoo
+  29foo
+  30bar
+  31foobar
+  32barfoo
+  33foo
+  34bar
+  35foobar
+  36barfoo
+  37foo
+  38bar
+  39foobar
+  40barfoo
+  41foo
+  42bar
+  43foobar
+  44barfoo
+  45foo
+  46bar
+  47foobar
+  48barfoo
+  49foo
+  50bar
+  51foobar
+  52barfoo
+  53foo
+  54bar
+  55foobar
+  56barfoo
+  57foo
+  58bar
+  59foobar
+  60barfoo
+  61foo
+  62bar
+  63foobar
+  64barfoo
+  65foo
+  66bar
+  67foobar
+  68barfoo
+  69foo
+  70bar
+  71foobar
+  72barfoo
+  73foo
+  74bar
+  75foobar
+  76barfoo
+  77foo
+  78bar
+  79foobar
+  80barfoo
+  81foo
+  82bar
+  83foobar
+  84barfoo
+  85foo
+  86bar
+  87foobar
+  88barfoo
+  89foo
+  90bar
+  91foobar
+  92barfoo
+  93foo
+  94bar
+  95foobar
+  96barfoo
+  -----END RSA PRIVATE KEY-----
+`))
+				So(err, ShouldNotBeNil)
+				So(f, ShouldBeNil)
+				So(err.Error(), ShouldEqual, "key-value delimiter not found: 1foo\n")
+			})
 
-		Convey("Allow unparseable sections", func() {
-			f, err := ini.LoadSources(ini.LoadOptions{
-				Insensitive:         true,
-				UnparseableSections: []string{"core_lesson", "comments"},
-			}, []byte(`
+			Convey("Allow unparsable sections", func() {
+				f, err := ini.LoadSources(ini.LoadOptions{
+					AllowPythonMultilineValues: false,
+					Insensitive:                true,
+					UnparseableSections:        []string{"core_lesson", "comments"},
+				}, []byte(`
 Lesson_Location = 87
 Lesson_Status = C
 Score = 3
@@ -297,20 +1155,20 @@ my lesson state data – 1111111111111111111000000000000000001110000
 
 [COMMENTS]
 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`))
-			So(err, ShouldBeNil)
-			So(f, ShouldNotBeNil)
+				So(err, ShouldBeNil)
+				So(f, ShouldNotBeNil)
 
-			So(f.Section("").Key("score").String(), ShouldEqual, "3")
-			So(f.Section("").Body(), ShouldBeEmpty)
-			So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
+				So(f.Section("").Key("score").String(), ShouldEqual, "3")
+				So(f.Section("").Body(), ShouldBeEmpty)
+				So(f.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
 111111111111111111100000000000111000000000 – end my lesson state data`)
-			So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
+				So(f.Section("comments").Body(), ShouldEqual, `<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>`)
 
-			Convey("Write out", func() {
-				var buf bytes.Buffer
-				_, err := f.WriteTo(&buf)
-				So(err, ShouldBeNil)
-				So(buf.String(), ShouldEqual, `lesson_location = 87
+				Convey("Write out", func() {
+					var buf bytes.Buffer
+					_, err := f.WriteTo(&buf)
+					So(err, ShouldBeNil)
+					So(buf.String(), ShouldEqual, `lesson_location = 87
 lesson_status   = C
 score           = 3
 time            = 00:02:30
@@ -322,15 +1180,103 @@ my lesson state data – 1111111111111111111000000000000000001110000
 [comments]
 <1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
 `)
-			})
+				})
 
-			Convey("Inverse case", func() {
-				_, err := ini.Load([]byte(`
+				Convey("Inverse case", func() {
+					_, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(`
 [CORE_LESSON]
 my lesson state data – 1111111111111111111000000000000000001110000
 111111111111111111100000000000111000000000 – end my lesson state data`))
-				So(err, ShouldNotBeNil)
+					So(err, ShouldNotBeNil)
+				})
+			})
+
+			Convey("And false `SpaceBeforeInlineComment`", func() {
+				Convey("Can't parse INI files containing `#` or `;` in value", func() {
+					f, err := ini.LoadSources(
+						ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: false},
+						[]byte(`
+[author]
+NAME = U#n#k#n#w#o#n
+GITHUB = U;n;k;n;w;o;n
+`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+					sec := f.Section("author")
+					nameValue := sec.Key("NAME").String()
+					githubValue := sec.Key("GITHUB").String()
+					So(nameValue, ShouldEqual, "U")
+					So(githubValue, ShouldEqual, "U")
+				})
+			})
+
+			Convey("And true `SpaceBeforeInlineComment`", func() {
+				Convey("Can parse INI files containing `#` or `;` in value", func() {
+					f, err := ini.LoadSources(
+						ini.LoadOptions{AllowPythonMultilineValues: true, SpaceBeforeInlineComment: true},
+						[]byte(`
+[author]
+NAME = U#n#k#n#w#o#n
+GITHUB = U;n;k;n;w;o;n
+`))
+					So(err, ShouldBeNil)
+					So(f, ShouldNotBeNil)
+					sec := f.Section("author")
+					nameValue := sec.Key("NAME").String()
+					githubValue := sec.Key("GITHUB").String()
+					So(nameValue, ShouldEqual, "U#n#k#n#w#o#n")
+					So(githubValue, ShouldEqual, "U;n;k;n;w;o;n")
+				})
 			})
 		})
 	})
 }
+
+func Test_KeyValueDelimiters(t *testing.T) {
+	Convey("Custom key-value delimiters", t, func() {
+		f, err := ini.LoadSources(ini.LoadOptions{
+			KeyValueDelimiters: "?!",
+		}, []byte(`
+[section]
+key1?value1
+key2!value2
+`))
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
+		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
+	})
+}
+
+func Test_PreserveSurroundedQuote(t *testing.T) {
+	Convey("Preserve surrounded quote test", t, func() {
+		f, err := ini.LoadSources(ini.LoadOptions{
+			PreserveSurroundedQuote: true,
+		}, []byte(`
+[section]
+key1 = "value1"
+key2 = value2
+`))
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		So(f.Section("section").Key("key1").String(), ShouldEqual, "\"value1\"")
+		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
+	})
+
+	Convey("Preserve surrounded quote test inverse test", t, func() {
+		f, err := ini.LoadSources(ini.LoadOptions{
+			PreserveSurroundedQuote: false,
+		}, []byte(`
+[section]
+key1 = "value1"
+key2 = value2
+`))
+		So(err, ShouldBeNil)
+		So(f, ShouldNotBeNil)
+
+		So(f.Section("section").Key("key1").String(), ShouldEqual, "value1")
+		So(f.Section("section").Key("key2").String(), ShouldEqual, "value2")
+	})
+}
diff --git a/key.go b/key.go
index 7c8566a..0fee0dc 100644
--- a/key.go
+++ b/key.go
@@ -133,8 +133,7 @@ func (k *Key) transformValue(val string) string {
 		}
 
 		// Take off leading '%(' and trailing ')s'.
-		noption := strings.TrimLeft(vr, "%(")
-		noption = strings.TrimRight(noption, ")s")
+		noption := vr[2 : len(vr)-2]
 
 		// Search in the same section.
 		nk, err := k.s.GetKey(noption)
@@ -187,23 +186,24 @@ func (k *Key) Float64() (float64, error) {
 
 // Int returns int type value.
 func (k *Key) Int() (int, error) {
-	return strconv.Atoi(k.String())
+    v, err := strconv.ParseInt(k.String(), 0, 64)
+    return int(v), err
 }
 
 // Int64 returns int64 type value.
 func (k *Key) Int64() (int64, error) {
-	return strconv.ParseInt(k.String(), 10, 64)
+	return strconv.ParseInt(k.String(), 0, 64)
 }
 
 // Uint returns uint type valued.
 func (k *Key) Uint() (uint, error) {
-	u, e := strconv.ParseUint(k.String(), 10, 64)
+	u, e := strconv.ParseUint(k.String(), 0, 64)
 	return uint(u), e
 }
 
 // Uint64 returns uint64 type value.
 func (k *Key) Uint64() (uint64, error) {
-	return strconv.ParseUint(k.String(), 10, 64)
+	return strconv.ParseUint(k.String(), 0, 64)
 }
 
 // Duration returns time.Duration type value.
@@ -668,7 +668,8 @@ func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]
 func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
 	vals := make([]int, 0, len(strs))
 	for _, str := range strs {
-		val, err := strconv.Atoi(str)
+		valInt64, err := strconv.ParseInt(str, 0, 64)
+		val := int(valInt64)        
 		if err != nil && returnOnInvalid {
 			return nil, err
 		}
@@ -683,7 +684,7 @@ func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int,
 func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
 	vals := make([]int64, 0, len(strs))
 	for _, str := range strs {
-		val, err := strconv.ParseInt(str, 10, 64)
+		val, err := strconv.ParseInt(str, 0, 64)
 		if err != nil && returnOnInvalid {
 			return nil, err
 		}
@@ -698,7 +699,7 @@ func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]in
 func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
 	vals := make([]uint, 0, len(strs))
 	for _, str := range strs {
-		val, err := strconv.ParseUint(str, 10, 0)
+		val, err := strconv.ParseUint(str, 0, 0)
 		if err != nil && returnOnInvalid {
 			return nil, err
 		}
@@ -713,7 +714,7 @@ func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uin
 func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
 	vals := make([]uint64, 0, len(strs))
 	for _, str := range strs {
-		val, err := strconv.ParseUint(str, 10, 64)
+		val, err := strconv.ParseUint(str, 0, 64)
 		if err != nil && returnOnInvalid {
 			return nil, err
 		}
diff --git a/key_test.go b/key_test.go
index a13ad95..c2f4425 100644
--- a/key_test.go
+++ b/key_test.go
@@ -216,6 +216,10 @@ func TestKey_Helpers(t *testing.T) {
 			So(err, ShouldBeNil)
 			So(v7.String(), ShouldEqual, t.String())
 
+			v8, err := sec.Key("HEX_NUMBER").Int()
+			So(err, ShouldBeNil)
+			So(v8, ShouldEqual, 0x3000)
+
 			Convey("Must get values with type", func() {
 				So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
 				So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
@@ -225,6 +229,7 @@ func TestKey_Helpers(t *testing.T) {
 				So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
 				So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
 				So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
+				So(sec.Key("HEX_NUMBER").MustInt(), ShouldEqual, 0x3000)
 
 				dur, err := time.ParseDuration("2h45m")
 				So(err, ShouldBeNil)
@@ -238,6 +243,7 @@ func TestKey_Helpers(t *testing.T) {
 					So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
 					So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
 					So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
+					So(sec.Key("HEX_NUMBER_404").MustInt(0x3001), ShouldEqual, 0x3001)
 
 					t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
 					So(err, ShouldBeNil)
@@ -255,6 +261,7 @@ func TestKey_Helpers(t *testing.T) {
 						So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
 						So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
 						So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
+						So(sec.Key("HEX_NUMBER_404").String(), ShouldEqual, "12289")
 					})
 				})
 			})
@@ -514,10 +521,13 @@ func TestRecursiveValues(t *testing.T) {
 	Convey("Recursive values should not reflect on same key", t, func() {
 		f, err := ini.Load([]byte(`
 NAME = ini
+expires = yes
 [package]
-NAME = %(NAME)s`))
+NAME = %(NAME)s
+expires = %(expires)s`))
 		So(err, ShouldBeNil)
 		So(f, ShouldNotBeNil)
 		So(f.Section("package").Key("NAME").String(), ShouldEqual, "ini")
+		So(f.Section("package").Key("expires").String(), ShouldEqual, "yes")
 	})
 }
diff --git a/parser.go b/parser.go
index db3af8f..f20073d 100644
--- a/parser.go
+++ b/parser.go
@@ -19,11 +19,14 @@ import (
 	"bytes"
 	"fmt"
 	"io"
+	"regexp"
 	"strconv"
 	"strings"
 	"unicode"
 )
 
+var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)")
+
 type tokenType int
 
 const (
@@ -97,7 +100,7 @@ func cleanComment(in []byte) ([]byte, bool) {
 	return in[i:], true
 }
 
-func readKeyName(in []byte) (string, int, error) {
+func readKeyName(delimiters string, in []byte) (string, int, error) {
 	line := string(in)
 
 	// Check if key name surrounded by quotes.
@@ -124,7 +127,7 @@ func readKeyName(in []byte) (string, int, error) {
 		pos += startIdx
 
 		// Find key-value delimiter
-		i := strings.IndexAny(line[pos+startIdx:], "=:")
+		i := strings.IndexAny(line[pos+startIdx:], delimiters)
 		if i < 0 {
 			return "", -1, ErrDelimiterNotFound{line}
 		}
@@ -132,7 +135,7 @@ func readKeyName(in []byte) (string, int, error) {
 		return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
 	}
 
-	endIdx = strings.IndexAny(line, "=:")
+	endIdx = strings.IndexAny(line, delimiters)
 	if endIdx < 0 {
 		return "", -1, ErrDelimiterNotFound{line}
 	}
@@ -194,7 +197,8 @@ func hasSurroundedQuote(in string, quote byte) bool {
 }
 
 func (p *parser) readValue(in []byte,
-	ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols bool) (string, error) {
+	parserBufferSize int,
+	ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment, preserveSurroundedQuote bool) (string, error) {
 
 	line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
 	if len(line) == 0 {
@@ -224,26 +228,39 @@ func (p *parser) readValue(in []byte,
 		return line[startIdx : pos+startIdx], nil
 	}
 
+	lastChar := line[len(line)-1]
 	// Won't be able to reach here if value only contains whitespace
 	line = strings.TrimSpace(line)
+	trimmedLastChar := line[len(line)-1]
 
 	// Check continuation lines when desired
-	if !ignoreContinuation && line[len(line)-1] == '\\' {
+	if !ignoreContinuation && trimmedLastChar == '\\' {
 		return p.readContinuationLines(line[:len(line)-1])
 	}
 
 	// Check if ignore inline comment
 	if !ignoreInlineComment {
-		i := strings.IndexAny(line, "#;")
+		var i int
+		if spaceBeforeInlineComment {
+			i = strings.Index(line, " #")
+			if i == -1 {
+				i = strings.Index(line, " ;")
+			}
+
+		} else {
+			i = strings.IndexAny(line, "#;")
+		}
+
 		if i > -1 {
 			p.comment.WriteString(line[i:])
 			line = strings.TrimSpace(line[:i])
 		}
+
 	}
 
 	// Trim single and double quotes
-	if hasSurroundedQuote(line, '\'') ||
-		hasSurroundedQuote(line, '"') {
+	if (hasSurroundedQuote(line, '\'') ||
+		hasSurroundedQuote(line, '"')) && !preserveSurroundedQuote {
 		line = line[1 : len(line)-1]
 	} else if len(valQuote) == 0 && unescapeValueCommentSymbols {
 		if strings.Contains(line, `\;`) {
@@ -252,7 +269,42 @@ func (p *parser) readValue(in []byte,
 		if strings.Contains(line, `\#`) {
 			line = strings.Replace(line, `\#`, "#", -1)
 		}
+	} else if allowPythonMultilines && lastChar == '\n' {
+		parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize)
+		peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+		val := line
+
+		for {
+			peekData, peekErr := peekBuffer.ReadBytes('\n')
+			if peekErr != nil {
+				if peekErr == io.EOF {
+					return val, nil
+				}
+				return "", peekErr
+			}
+
+			peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+			if len(peekMatches) != 3 {
+				return val, nil
+			}
+
+			// NOTE: Return if not a python-ini multi-line value.
+			currentIdentSize := len(peekMatches[1])
+			if currentIdentSize <= 0 {
+				return val, nil
+			}
+
+			// NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer.
+			_, err := p.readUntil('\n')
+			if err != nil {
+				return "", err
+			}
+
+			val += fmt.Sprintf("\n%s", peekMatches[2])
+		}
 	}
+
 	return line, nil
 }
 
@@ -276,6 +328,28 @@ func (f *File) parse(reader io.Reader) (err error) {
 
 	var line []byte
 	var inUnparseableSection bool
+
+	// NOTE: Iterate and increase `currentPeekSize` until
+	// the size of the parser buffer is found.
+	// TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+	parserBufferSize := 0
+	// NOTE: Peek 1kb at a time.
+	currentPeekSize := 1024
+
+	if f.options.AllowPythonMultilineValues {
+		for {
+			peekBytes, _ := p.buf.Peek(currentPeekSize)
+			peekBytesLength := len(peekBytes)
+
+			if parserBufferSize >= peekBytesLength {
+				break
+			}
+
+			currentPeekSize *= 2
+			parserBufferSize = peekBytesLength
+		}
+	}
+
 	for !p.isEOF {
 		line, err = p.readUntil('\n')
 		if err != nil {
@@ -307,8 +381,7 @@ func (f *File) parse(reader io.Reader) (err error) {
 		// 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("]"))
+			closeIdx := bytes.LastIndexByte(line, ']')
 			if closeIdx == -1 {
 				return fmt.Errorf("unclosed section: %s", line)
 			}
@@ -347,25 +420,35 @@ func (f *File) parse(reader io.Reader) (err error) {
 			continue
 		}
 
-		kname, offset, err := readKeyName(line)
+		kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
 		if err != nil {
 			// Treat as boolean key when desired, and whole line is key name.
-			if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
-				kname, err := p.readValue(line,
-					f.options.IgnoreContinuation,
-					f.options.IgnoreInlineComment,
-					f.options.UnescapeValueDoubleQuotes,
-					f.options.UnescapeValueCommentSymbols)
-				if err != nil {
-					return err
-				}
-				key, err := section.NewBooleanKey(kname)
-				if err != nil {
-					return err
+			if IsErrDelimiterNotFound(err) {
+				switch {
+				case f.options.AllowBooleanKeys:
+					kname, err := p.readValue(line,
+						parserBufferSize,
+						f.options.IgnoreContinuation,
+						f.options.IgnoreInlineComment,
+						f.options.UnescapeValueDoubleQuotes,
+						f.options.UnescapeValueCommentSymbols,
+						f.options.AllowPythonMultilineValues,
+						f.options.SpaceBeforeInlineComment,
+						f.options.PreserveSurroundedQuote)
+					if err != nil {
+						return err
+					}
+					key, err := section.NewBooleanKey(kname)
+					if err != nil {
+						return err
+					}
+					key.Comment = strings.TrimSpace(p.comment.String())
+					p.comment.Reset()
+					continue
+
+				case f.options.SkipUnrecognizableLines:
+					continue
 				}
-				key.Comment = strings.TrimSpace(p.comment.String())
-				p.comment.Reset()
-				continue
 			}
 			return err
 		}
@@ -379,10 +462,14 @@ func (f *File) parse(reader io.Reader) (err error) {
 		}
 
 		value, err := p.readValue(line[offset:],
+			parserBufferSize,
 			f.options.IgnoreContinuation,
 			f.options.IgnoreInlineComment,
 			f.options.UnescapeValueDoubleQuotes,
-			f.options.UnescapeValueCommentSymbols)
+			f.options.UnescapeValueCommentSymbols,
+			f.options.AllowPythonMultilineValues,
+			f.options.SpaceBeforeInlineComment,
+			f.options.PreserveSurroundedQuote)
 		if err != nil {
 			return err
 		}
diff --git a/parser_test.go b/parser_test.go
index bb0f266..bd3c6ac 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -28,7 +28,7 @@ func TestBOM(t *testing.T) {
 			So(err, ShouldBeNil)
 			So(f, ShouldNotBeNil)
 
-			So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
+			So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "example@email.com")
 		})
 
 		Convey("UTF-16-LE-BOM", func() {
diff --git a/section.go b/section.go
index d8a4026..bc32c62 100644
--- a/section.go
+++ b/section.go
@@ -82,6 +82,7 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
 			}
 		} else {
 			s.keys[name].value = val
+			s.keysHash[name] = val
 		}
 		return s.keys[name], nil
 	}
@@ -237,6 +238,7 @@ func (s *Section) DeleteKey(name string) {
 		if k == name {
 			s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
 			delete(s.keys, name)
+			delete(s.keysHash, name)
 			return
 		}
 	}
diff --git a/section_test.go b/section_test.go
index e9c3478..fd5e43a 100644
--- a/section_test.go
+++ b/section_test.go
@@ -272,24 +272,26 @@ func TestSection_KeyStrings(t *testing.T) {
 
 func TestSection_KeyHash(t *testing.T) {
 	Convey("Get clone of key hash", t, func() {
-		f := ini.Empty()
+		f, err := ini.Load([]byte(`
+key = one
+[log]
+name = app
+file = a.log
+`), []byte(`
+key = two
+[log]
+name = app2
+file = b.log
+`))
+		So(err, ShouldBeNil)
 		So(f, ShouldNotBeNil)
 
-		k, err := f.Section("").NewKey("NAME", "ini")
-		So(err, ShouldBeNil)
-		So(k, ShouldNotBeNil)
-		k, err = f.Section("").NewKey("VERSION", "v1")
-		So(err, ShouldBeNil)
-		So(k, ShouldNotBeNil)
-		k, err = f.Section("").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
-		So(err, ShouldBeNil)
-		So(k, ShouldNotBeNil)
+		So(f.Section("").Key("key").String(), ShouldEqual, "two")
 
-		hash := f.Section("").KeysHash()
+		hash := f.Section("log").KeysHash()
 		relation := map[string]string{
-			"NAME":        "ini",
-			"VERSION":     "v1",
-			"IMPORT_PATH": "gopkg.in/ini.v1",
+			"name": "app2",
+			"file": "b.log",
 		}
 		for k, v := range hash {
 			So(v, ShouldEqual, relation[k])
diff --git a/struct.go b/struct.go
index 9719dc6..a9dfed0 100644
--- a/struct.go
+++ b/struct.go
@@ -180,7 +180,7 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
 	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 		durationVal, err := key.Duration()
 		// Skip zero value
-		if err == nil && int(durationVal) > 0 {
+		if err == nil && uint64(durationVal) > 0 {
 			field.Set(reflect.ValueOf(durationVal))
 			return nil
 		}
diff --git a/struct_test.go b/struct_test.go
index 75987ea..0dbbef2 100644
--- a/struct_test.go
+++ b/struct_test.go
@@ -37,7 +37,7 @@ type testNested struct {
 	Unused      int `ini:"-"`
 }
 
-type testEmbeded struct {
+type TestEmbeded struct {
 	GPA float64
 }
 
@@ -49,7 +49,7 @@ type testStruct struct {
 	Born         time.Time
 	Time         time.Duration `ini:"Duration"`
 	Others       testNested
-	*testEmbeded `ini:"grade"`
+	*TestEmbeded `ini:"grade"`
 	Unused       int `ini:"-"`
 	Unsigned     uint
 	Omitted      bool     `ini:"omitthis,omitempty"`
@@ -97,12 +97,12 @@ type unsupport2 struct {
 	}
 }
 
-type unsupport3 struct {
+type Unsupport3 struct {
 	Cities byte
 }
 
 type unsupport4 struct {
-	*unsupport3 `ini:"Others"`
+	*Unsupport3 `ini:"Others"`
 }
 
 type defaultValue struct {
@@ -155,7 +155,7 @@ func Test_MapToStruct(t *testing.T) {
 			So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
 			So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
 			So(ts.Others.Note, ShouldEqual, "Hello world!")
-			So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
+			So(ts.TestEmbeded.GPA, ShouldEqual, 2.8)
 		})
 
 		Convey("Map section to struct", func() {
diff --git a/testdata/TestFile_WriteTo.golden b/testdata/TestFile_WriteTo.golden
new file mode 100644
index 0000000..6e5d510
--- /dev/null
+++ b/testdata/TestFile_WriteTo.golden
@@ -0,0 +1,87 @@
+; 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]
+; This is author name
+NAME   = Unknwon
+E-MAIL = u@gogs.io
+GITHUB = https://github.com/%(NAME)s
+# Succeeding comment
+BIO    = """Gopher.
+Coding addict.
+Good man.
+"""
+
+[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
+DURATION   = 2h45m
+UINT       = 3
+HEX_NUMBER = 0x3000
+
+[array]
+STRINGS  = en, zh, de
+FLOAT64S = 1.1, 2.2, 3.3
+INTS     = 1, 2, 3
+UINTS    = 1, 2, 3
+TIMES    = 2015-01-01T20:17:05Z,2015-01-01T20:17:05Z,2015-01-01T20:17:05Z
+
+[note]
+empty_lines = next line is empty
+boolean_key
+more        = notes
+
+; Comment before the section
+; This is a comment for the section too
+[comments]
+; Comment before key
+key  = value
+; This is a comment for key2
+key2 = value2
+key3 = "one", "two", "three"
+
+[string escapes]
+key1 = value1, value2, value3
+key2 = value1\, value2
+key3 = val\ue1, value2
+key4 = value1\\, value\\\\2
+key5 = value1\,, value2
+key6 = aaa bbb\ and\ space ccc
+
+[advance]
+value with quotes      = some value
+value quote2 again     = some value
+includes comment sign  = `my#password`
+includes comment sign2 = `my;password`
+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"""
+two_lines              = how about continuation lines?
+lots_of_lines          = 1 2 3 4 
+
diff --git a/testdata/UTF-16-BE-BOM.ini b/testdata/UTF-16-BE-BOM.ini
index c8bf82c..04efd32 100644
Binary files a/testdata/UTF-16-BE-BOM.ini and b/testdata/UTF-16-BE-BOM.ini differ
diff --git a/testdata/UTF-16-LE-BOM.ini b/testdata/UTF-16-LE-BOM.ini
index 27f6218..5a9fe5b 100644
Binary files a/testdata/UTF-16-LE-BOM.ini and b/testdata/UTF-16-LE-BOM.ini differ
diff --git a/testdata/UTF-8-BOM.ini b/testdata/UTF-8-BOM.ini
index 2ed0ac1..abca4ec 100644
--- a/testdata/UTF-8-BOM.ini
+++ b/testdata/UTF-8-BOM.ini
@@ -1,2 +1,2 @@
 [author]
-E-MAIL = u@gogs.io
\ No newline at end of file
+E-MAIL = example@email.com
\ No newline at end of file
diff --git a/testdata/full.ini b/testdata/full.ini
index 469b1f1..d84142e 100644
--- a/testdata/full.ini
+++ b/testdata/full.ini
@@ -36,6 +36,7 @@ INT        = 10
 TIME       = 2015-01-01T20:17:05Z
 DURATION   = 2h45m
 UINT       = 3
+HEX_NUMBER = 0x3000
 
 [array]
 STRINGS  = en, zh, de