// 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="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 %s`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: true}, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 %s"`) }) }) Convey("Unescape comment symbols inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: true, IgnoreInlineComment: true, UnescapeValueCommentSymbols: true, }, []byte(` key = test value more text `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key").String(), ShouldEqual, `test value more text`) }) 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> This slide has the fuel listed in the wrong units `)) 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> This slide has the fuel listed in the wrong units `) 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> This slide has the fuel listed in the wrong units `) }) 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="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 %s`) Convey("Inverse case", func() { f, err := ini.LoadSources(ini.LoadOptions{AllowPythonMultilineValues: false}, []byte(` create_repo="创建了仓库 %s"`)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 %s"`) }) }) Convey("Unescape comment symbols inside value", func() { f, err := ini.LoadSources(ini.LoadOptions{ AllowPythonMultilineValues: false, IgnoreInlineComment: true, UnescapeValueCommentSymbols: true, }, []byte(` key = test value more text `)) So(err, ShouldBeNil) So(f, ShouldNotBeNil) So(f.Section("").Key("key").String(), ShouldEqual, `test value more text`) }) 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> This slide has the fuel listed in the wrong units `)) 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> This slide has the fuel listed in the wrong units `) 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> This slide has the fuel listed in the wrong units `) }) 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") }) }