// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package ini_test

import (
	"bytes"
	"flag"
	"io/ioutil"
	"testing"

	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/ini.v1"
)

const (
	confData = `
	; Package name
	NAME        = ini
	; Package version
	VERSION     = v1
	; Package import path
	IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
	
	# Information about package author
	# Bio can be written in multiple lines.
	[author]
	NAME   = Unknwon  ; Succeeding comment
	E-MAIL = fake@localhost
	GITHUB = https://github.com/%(NAME)s
	BIO    = """Gopher.
	Coding addict.
	Good man.
	"""  # Succeeding comment`
	minimalConf  = "testdata/minimal.ini"
	fullConf     = "testdata/full.ini"
	notFoundConf = "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(
			"testdata/minimal.ini",
			[]byte("NAME = ini\nIMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s"),
			bytes.NewReader([]byte(`VERSION = v1`)),
			ioutil.NopCloser(bytes.NewReader([]byte("[author]\nNAME = Unknwon"))),
		)
		So(err, ShouldBeNil)
		So(f, ShouldNotBeNil)

		// 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")
		So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")

		sec = f.Section("author")
		So(sec.Key("NAME").String(), ShouldEqual, "Unknwon")
		So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io")
	})

	Convey("Load from bad data sources", t, func() {
		Convey("Invalid input", func() {
			_, err := ini.Load(notFoundConf)
			So(err, ShouldNotBeNil)
		})

		Convey("Unsupported type", func() {
			_, err := ini.Load(123)
			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 TestLooseLoad(t *testing.T) {
	Convey("Load from data sources with option `Loose` true", t, func() {
		f, err := ini.LoadSources(ini.LoadOptions{Loose: true}, notFoundConf, minimalConf)
		So(err, ShouldBeNil)
		So(f, ShouldNotBeNil)

		Convey("Inverse case", func() {
			_, err = ini.Load(notFoundConf)
			So(err, ShouldNotBeNil)
		})
	})
}

func TestInsensitiveLoad(t *testing.T) {
	Convey("Insensitive to section and key names", t, func() {
		f, err := ini.InsensitiveLoad(minimalConf)
		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.Load(minimalConf)
			So(err, ShouldBeNil)
			So(f, ShouldNotBeNil)

			So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
		})
	})

	// Ref: https://github.com/go-ini/ini/issues/198
	Convey("Insensitive load with default section", t, func() {
		f, err := ini.InsensitiveLoad([]byte(`
user = unknwon
[profile]
email = unknwon@local
`))
		So(err, ShouldBeNil)
		So(f, ShouldNotBeNil)

		So(f.Section(ini.DefaultSection).Key("user").String(), ShouldEqual, "unknwon")
	})
}

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}, notFoundConf, minimalConf)
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				Convey("Inverse case", func() {
					_, err = ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, notFoundConf)
					So(err, ShouldNotBeNil)
				})
			})

			Convey("Insensitive to section and key names", func() {
				f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true, Insensitive: true}, minimalConf)
				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: true}, minimalConf)
					So(err, ShouldBeNil)
					So(f, ShouldNotBeNil)

					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
				})
			})

			Convey("Insensitive to sections and sensitive to key names", func() {
				f, err := ini.LoadSources(ini.LoadOptions{InsensitiveSections: true}, minimalConf)
				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{}, minimalConf)
					So(err, ShouldBeNil)
					So(f, ShouldNotBeNil)

					So(f.Section("Author").Key("e-mail").String(), ShouldBeEmpty)
				})
			})

			Convey("Sensitive to sections and insensitive to key names", func() {
				f, err := ini.LoadSources(ini.LoadOptions{InsensitiveKeys: true}, minimalConf)
				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{}, minimalConf)
					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: true,
					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: true}, []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: true,
					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: 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("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{
					AllowPythonMultilineValues: true,
					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: true}, []byte(`
key1=hello
#key2
key3`))
					So(err, ShouldNotBeNil)
				})
			})

			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(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/*

`)
				})

				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-----
multiline_list =
  first
  second
  third
`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				So(f.Section("long").Key("long_rsa_private_key").String(), ShouldEqual, "-----BEGIN RSA PRIVATE KEY-----\nfoo\nbar\nfoobar\nbarfoo\n-----END RSA PRIVATE KEY-----")
				So(f.Section("long").Key("multiline_list").String(), ShouldEqual, "\nfirst\nsecond\nthird")
			})

			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("with false `AllowPythonMultilineValues`", func() {
			Convey("Ignore nonexistent files", func() {
				f, err := ini.LoadSources(ini.LoadOptions{
					AllowPythonMultilineValues: false,
					Loose:                      true,
				}, notFoundConf, minimalConf)
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				Convey("Inverse case", func() {
					_, err = ini.LoadSources(ini.LoadOptions{
						AllowPythonMultilineValues: false,
					}, notFoundConf)
					So(err, ShouldNotBeNil)
				})
			})

			Convey("Insensitive to section and key names", func() {
				f, err := ini.LoadSources(ini.LoadOptions{
					AllowPythonMultilineValues: false,
					Insensitive:                true,
				}, minimalConf)
				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,
					}, minimalConf)
					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/*

`)
				})

				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>`)

				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{
					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(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 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
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: false}, []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: 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")
				})
			})
		})

		Convey("with `ChildSectionDelimiter` ':'", func() {
			Convey("Get all keys of parent sections", func() {
				f := ini.Empty(ini.LoadOptions{ChildSectionDelimiter: ":"})
				So(f, ShouldNotBeNil)

				k, err := f.Section("package").NewKey("NAME", "ini")
				So(err, ShouldBeNil)
				So(k, ShouldNotBeNil)
				k, err = f.Section("package").NewKey("VERSION", "v1")
				So(err, ShouldBeNil)
				So(k, ShouldNotBeNil)
				k, err = f.Section("package").NewKey("IMPORT_PATH", "gopkg.in/ini.v1")
				So(err, ShouldBeNil)
				So(k, ShouldNotBeNil)

				keys := f.Section("package:sub:sub2").ParentKeys()
				names := []string{"NAME", "VERSION", "IMPORT_PATH"}
				So(len(keys), ShouldEqual, len(names))
				for i, name := range names {
					So(keys[i].Name(), ShouldEqual, name)
				}
			})

			Convey("Getting and setting values", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ChildSectionDelimiter: ":"}, fullConf)
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				Convey("Get parent-keys that are available to the child section", func() {
					parentKeys := f.Section("package:sub").ParentKeys()
					So(parentKeys, ShouldNotBeNil)
					for _, k := range parentKeys {
						So(k.Name(), ShouldEqual, "CLONE_URL")
					}
				})

				Convey("Get parent section value", func() {
					So(f.Section("package:sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
					So(f.Section("package:fake:sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1")
				})
			})

			Convey("Get child sections by parent name", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ChildSectionDelimiter: ":"}, []byte(`
[node]
[node:biz1]
[node:biz2]
[node.biz3]
[node.bizN]
`))
				So(err, ShouldBeNil)
				So(f, ShouldNotBeNil)

				children := f.ChildSections("node")
				names := []string{"node:biz1", "node:biz2"}
				So(len(children), ShouldEqual, len(names))
				for i, name := range names {
					So(children[i].Name(), ShouldEqual, name)
				}
			})
		})

		Convey("ShortCircuit", func() {
			Convey("Load the first available configuration, ignore other configuration", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ShortCircuit: true}, minimalConf, []byte(`key1 = value1`))
				So(f, ShouldNotBeNil)
				So(err, ShouldBeNil)
				var buf bytes.Buffer
				_, err = f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `[author]
E-MAIL = u@gogs.io

`)
			})

			Convey("Return an error when fail to load", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ShortCircuit: true}, notFoundConf, minimalConf)
				So(f, ShouldBeNil)
				So(err, ShouldNotBeNil)
			})

			Convey("Used with Loose to ignore errors that the file does not exist", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ShortCircuit: true, Loose: true}, notFoundConf, minimalConf)
				So(f, ShouldNotBeNil)
				So(err, ShouldBeNil)
				var buf bytes.Buffer
				_, err = f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `[author]
E-MAIL = u@gogs.io

`)
			})

			Convey("Ensure all sources are loaded without ShortCircuit", func() {
				f, err := ini.LoadSources(ini.LoadOptions{ShortCircuit: false}, minimalConf, []byte(`key1 = value1`))
				So(f, ShouldNotBeNil)
				So(err, ShouldBeNil)
				var buf bytes.Buffer
				_, err = f.WriteTo(&buf)
				So(err, ShouldBeNil)
				So(buf.String(), ShouldEqual, `key1 = value1

[author]
E-MAIL = u@gogs.io

`)
			})
		})
	})
}

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")
	})
}
