From 7006dbe48cbbbcd3908e1335e34c121055f9bcd4 Mon Sep 17 00:00:00 2001 From: Unknwon Date: Mon, 23 Oct 2017 06:27:56 -0400 Subject: [PATCH] TESTING: improve overall code quality --- Makefile | 5 +- bench_test.go | 118 +++++ file.go | 22 +- file_test.go | 343 +++++++++++++ ini.go | 14 +- ini_internal_test.go | 35 ++ ini_test.go | 757 +++++++++++------------------ key.go | 3 +- key_test.go | 383 ++++++--------- parser_test.go | 49 +- section.go | 9 + section_test.go | 314 ++++++++++-- struct_test.go | 65 +-- testdata/aicc.ini | 11 - testdata/full.ini | 83 ++++ testdata/{conf.ini => minimal.ini} | 0 16 files changed, 1380 insertions(+), 831 deletions(-) create mode 100644 bench_test.go create mode 100644 file_test.go create mode 100644 ini_internal_test.go delete mode 100644 testdata/aicc.ini create mode 100644 testdata/full.ini rename testdata/{conf.ini => minimal.ini} (100%) diff --git a/Makefile b/Makefile index ac034e5..1316911 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test bench vet +.PHONY: build test bench vet coverage build: vet bench @@ -10,3 +10,6 @@ bench: vet: go vet + +coverage: + go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out \ No newline at end of file diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..fc18024 --- /dev/null +++ b/bench_test.go @@ -0,0 +1,118 @@ +// Copyright 2017 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini_test + +import ( + "testing" + + "gopkg.in/ini.v1" +) + +func newTestFile(block bool) *ini.File { + c, _ := ini.Load([]byte(_CONF_DATA)) + c.BlockMode = block + return c +} + +func Benchmark_Key_Value(b *testing.B) { + c := newTestFile(true) + for i := 0; i < b.N; i++ { + c.Section("").Key("NAME").Value() + } +} + +func Benchmark_Key_Value_NonBlock(b *testing.B) { + c := newTestFile(false) + for i := 0; i < b.N; i++ { + c.Section("").Key("NAME").Value() + } +} + +func Benchmark_Key_Value_ViaSection(b *testing.B) { + c := newTestFile(true) + sec := c.Section("") + for i := 0; i < b.N; i++ { + sec.Key("NAME").Value() + } +} + +func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) { + c := newTestFile(false) + sec := c.Section("") + for i := 0; i < b.N; i++ { + sec.Key("NAME").Value() + } +} + +func Benchmark_Key_Value_Direct(b *testing.B) { + c := newTestFile(true) + key := c.Section("").Key("NAME") + for i := 0; i < b.N; i++ { + key.Value() + } +} + +func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) { + c := newTestFile(false) + key := c.Section("").Key("NAME") + for i := 0; i < b.N; i++ { + key.Value() + } +} + +func Benchmark_Key_String(b *testing.B) { + c := newTestFile(true) + for i := 0; i < b.N; i++ { + _ = c.Section("").Key("NAME").String() + } +} + +func Benchmark_Key_String_NonBlock(b *testing.B) { + c := newTestFile(false) + for i := 0; i < b.N; i++ { + _ = c.Section("").Key("NAME").String() + } +} + +func Benchmark_Key_String_ViaSection(b *testing.B) { + c := newTestFile(true) + sec := c.Section("") + for i := 0; i < b.N; i++ { + _ = sec.Key("NAME").String() + } +} + +func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) { + c := newTestFile(false) + sec := c.Section("") + for i := 0; i < b.N; i++ { + _ = sec.Key("NAME").String() + } +} + +func Benchmark_Key_SetValue(b *testing.B) { + c := newTestFile(true) + for i := 0; i < b.N; i++ { + c.Section("").Key("NAME").SetValue("10") + } +} + +func Benchmark_Key_SetValue_VisSection(b *testing.B) { + c := newTestFile(true) + sec := c.Section("") + for i := 0; i < b.N; i++ { + sec.Key("NAME").SetValue("10") + } +} diff --git a/file.go b/file.go index 5150960..1ba73ac 100644 --- a/file.go +++ b/file.go @@ -27,20 +27,17 @@ import ( // File represents a combination of a or more INI file(s) in memory. type File struct { + options LoadOptions + dataSources []dataSource + // Should make things safe, but sometimes doesn't matter. BlockMode bool - // Make sure data is safe in multiple goroutines. - lock sync.RWMutex - - // Allow combination of multiple data sources. - dataSources []dataSource - // Actual data is stored here. - sections map[string]*Section + lock sync.RWMutex // To keep data in order. sectionList []string - - options LoadOptions + // Actual data is stored here. + sections map[string]*Section NameMapper ValueMapper @@ -259,6 +256,13 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { if _, err := buf.WriteString(sec.rawBody); err != nil { return nil, err } + + if PrettySection { + // Put a line between sections + if _, err := buf.WriteString(LineBreak); err != nil { + return nil, err + } + } continue } diff --git a/file_test.go b/file_test.go new file mode 100644 index 0000000..215cda5 --- /dev/null +++ b/file_test.go @@ -0,0 +1,343 @@ +// Copyright 2017 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini_test + +import ( + "bytes" + "testing" + + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" +) + +func TestEmpty(t *testing.T) { + Convey("Create an empty object", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + // Should only have the default section + So(len(f.Sections()), ShouldEqual, 1) + + // Default section should not contain any key + So(len(f.Section("").Keys()), ShouldBeZeroValue) + }) +} + +func TestFile_NewSection(t *testing.T) { + Convey("Create a new section", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + sec, err := f.NewSection("author") + So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + So(sec.Name(), ShouldEqual, "author") + + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"}) + + Convey("With duplicated name", func() { + sec, err := f.NewSection("author") + So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + + // Does nothing if section already exists + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author"}) + }) + + Convey("With empty string", func() { + _, err := f.NewSection("") + So(err, ShouldNotBeNil) + }) + }) +} + +func TestFile_NewRawSection(t *testing.T) { + Convey("Create a new raw section", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000 +111111111111111111100000000000111000000000`) + So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + So(sec.Name(), ShouldEqual, "comments") + + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"}) + So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000 +111111111111111111100000000000111000000000`) + + Convey("With duplicated name", func() { + sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000`) + So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "comments"}) + + // Overwrite previous existed section + So(f.Section("comments").Body(), ShouldEqual, `1111111111111111111000000000000000001110000`) + }) + + Convey("With empty string", func() { + _, err := f.NewRawSection("", "") + So(err, ShouldNotBeNil) + }) + }) +} + +func TestFile_NewSections(t *testing.T) { + Convey("Create new sections", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + So(f.NewSections("package", "author"), ShouldBeNil) + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author"}) + + Convey("With duplicated name", func() { + So(f.NewSections("author", "features"), ShouldBeNil) + + // Ignore section already exists + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "package", "author", "features"}) + }) + + Convey("With empty string", func() { + So(f.NewSections("", ""), ShouldNotBeNil) + }) + }) +} + +func TestFile_GetSection(t *testing.T) { + Convey("Get a section", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + sec, err := f.GetSection("author") + So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + So(sec.Name(), ShouldEqual, "author") + + Convey("Section not exists", func() { + _, err := f.GetSection("404") + So(err, ShouldNotBeNil) + }) + }) +} + +func TestFile_Section(t *testing.T) { + Convey("Get a section", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + sec := f.Section("author") + So(sec, ShouldNotBeNil) + So(sec.Name(), ShouldEqual, "author") + + Convey("Section not exists", func() { + sec := f.Section("404") + So(sec, ShouldNotBeNil) + So(sec.Name(), ShouldEqual, "404") + }) + }) +} + +func TestFile_Sections(t *testing.T) { + Convey("Get all sections", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + secs := f.Sections() + names := []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} + So(len(secs), ShouldEqual, len(names)) + for i, name := range names { + So(secs[i].Name(), ShouldEqual, name) + } + }) +} + +func TestFile_ChildSections(t *testing.T) { + Convey("Get child sections by parent name", t, func() { + f, err := ini.Load([]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", "node.biz3", "node.bizN"} + So(len(children), ShouldEqual, len(names)) + for i, name := range names { + So(children[i].Name(), ShouldEqual, name) + } + }) +} + +func TestFile_SectionStrings(t *testing.T) { + Convey("Get all section names", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + So(f.SectionStrings(), ShouldResemble, []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"}) + }) +} + +func TestFile_DeleteSection(t *testing.T) { + Convey("Delete a section", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + f.NewSections("author", "package", "features") + f.DeleteSection("features") + f.DeleteSection("") + So(f.SectionStrings(), ShouldResemble, []string{"author", "package"}) + }) +} + +func TestFile_Append(t *testing.T) { + Convey("Append a data source", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + So(f.Append(_MINIMAL_CONF, []byte(` +[author] +NAME = Unknwon`)), ShouldBeNil) + + Convey("With bad input", func() { + So(f.Append(123), ShouldNotBeNil) + So(f.Append(_MINIMAL_CONF, 123), ShouldNotBeNil) + }) + }) +} + +func TestFile_WriteTo(t *testing.T) { + Convey("Write content to somewhere", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + f.Section("author").Comment = `Information about package author +# Bio can be written in multiple lines.` + f.Section("author").Key("NAME").Comment = "This is author name" + f.Section("note").NewBooleanKey("boolean_key") + f.Section("note").NewKey("more", "notes") + + 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 + +`) + }) +} + +func TestFile_SaveTo(t *testing.T) { + Convey("Write content to somewhere", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + So(f.SaveTo("testdata/conf_out.ini"), ShouldBeNil) + So(f.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil) + }) +} diff --git a/ini.go b/ini.go index 54a88f5..135f068 100644 --- a/ini.go +++ b/ini.go @@ -32,7 +32,7 @@ const ( // Maximum allowed depth when recursively substituing variable names. _DEPTH_VALUES = 99 - _VERSION = "1.30.1" + _VERSION = "1.30.2" ) // Version returns current package version literal. @@ -89,18 +89,6 @@ func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { return os.Open(s.name) } -type bytesReadCloser struct { - reader io.Reader -} - -func (rc *bytesReadCloser) Read(p []byte) (n int, err error) { - return rc.reader.Read(p) -} - -func (rc *bytesReadCloser) Close() error { - return nil -} - // sourceData represents an object that contains content in memory. type sourceData struct { data []byte diff --git a/ini_internal_test.go b/ini_internal_test.go new file mode 100644 index 0000000..257ef1e --- /dev/null +++ b/ini_internal_test.go @@ -0,0 +1,35 @@ +// Copyright 2017 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_Version(t *testing.T) { + Convey("Get version", t, func() { + So(Version(), ShouldEqual, _VERSION) + }) +} + +func Test_isSlice(t *testing.T) { + Convey("Check if a string is in the slice", t, func() { + ss := []string{"a", "b", "c"} + So(inSlice("a", ss), ShouldBeTrue) + So(inSlice("d", ss), ShouldBeFalse) + }) +} diff --git a/ini_test.go b/ini_test.go index c20ff3d..7a1efe4 100644 --- a/ini_test.go +++ b/ini_test.go @@ -12,507 +12,312 @@ // License for the specific language governing permissions and limitations // under the License. -package ini +package ini_test import ( "bytes" "io/ioutil" - "strings" "testing" - "time" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) -func Test_Version(t *testing.T) { - Convey("Get version", t, func() { - So(Version(), ShouldEqual, _VERSION) - }) -} - -const _CONF_DATA = ` -; Package name -NAME = ini -; Package version -VERSION = v1 -; Package import path -IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s - -# Information about package author -# Bio can be written in multiple lines. -[author] -NAME = Unknwon ; Succeeding comment -E-MAIL = fake@localhost -GITHUB = https://github.com/%(NAME)s -BIO = """Gopher. -Coding addict. -Good man. -""" # Succeeding comment - -[package] -CLONE_URL = https://%(IMPORT_PATH)s - -[package.sub] -UNUSED_KEY = should be deleted - -[features] --: Support read/write comments of keys and sections --: Support auto-increment of key names --: Support load multiple files to overwrite key values - -[types] -STRING = str -BOOL = true -BOOL_FALSE = false -FLOAT64 = 1.25 -INT = 10 -TIME = 2015-01-01T20:17:05Z -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\ - -; Comment before the section -[comments] ; This is a comment for the section too -; Comment before key -key = "value" -key2 = "value2" ; This is a comment for key2 -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 \ -` - -func Test_Load(t *testing.T) { - Convey("Load from data sources", t, func() { - - Convey("Load with empty data", func() { - So(Empty(), ShouldNotBeNil) - }) - - Convey("Load with multiple data sources", func() { - cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini", ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA)))) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - f, err := Load([]byte(_CONF_DATA), "testdata/404.ini") - So(err, ShouldNotBeNil) - So(f, ShouldBeNil) - }) - - Convey("Load with io.ReadCloser", func() { - cfg, err := Load(ioutil.NopCloser(bytes.NewReader([]byte(_CONF_DATA)))) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(cfg.Section("").Key("NAME").String(), ShouldEqual, "ini") - }) - }) - - Convey("Bad load process", t, func() { - - Convey("Load from invalid data sources", func() { - _, err := Load(_CONF_DATA) - So(err, ShouldNotBeNil) - - f, err := Load("testdata/404.ini") - So(err, ShouldNotBeNil) - So(f, ShouldBeNil) - - _, err = Load(1) - So(err, ShouldNotBeNil) - - _, err = Load([]byte(""), 1) - So(err, ShouldNotBeNil) - }) - - Convey("Load with bad section name", func() { - _, err := Load([]byte("[]")) - So(err, ShouldNotBeNil) - - _, err = Load([]byte("[")) - So(err, ShouldNotBeNil) - }) - - Convey("Load with bad keys", func() { - _, err := Load([]byte(`"""name`)) - So(err, ShouldNotBeNil) - - _, err = Load([]byte(`"""name"""`)) - So(err, ShouldNotBeNil) - - _, err = Load([]byte(`""=1`)) - So(err, ShouldNotBeNil) - - _, err = Load([]byte(`=`)) - So(err, ShouldNotBeNil) - - _, err = Load([]byte(`name`)) - So(err, ShouldNotBeNil) - }) - - Convey("Load with bad values", func() { - _, err := Load([]byte(`name="""Unknwon`)) - So(err, ShouldNotBeNil) - }) - }) - - Convey("Get section and key insensitively", t, func() { - cfg, err := InsensitiveLoad([]byte(_CONF_DATA), "testdata/conf.ini") - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - sec, err := cfg.GetSection("Author") - So(err, ShouldBeNil) - So(sec, ShouldNotBeNil) - - key, err := sec.GetKey("E-mail") - So(err, ShouldBeNil) - So(key, ShouldNotBeNil) - }) - - Convey("Load with ignoring continuation lines", t, func() { - cfg, err := LoadSources(LoadOptions{IgnoreContinuation: true}, []byte(`key1=a\b\ -key2=c\d\`)) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(cfg.Section("").Key("key1").String(), ShouldEqual, `a\b\`) - So(cfg.Section("").Key("key2").String(), ShouldEqual, `c\d\`) - }) - - Convey("Load with ignoring inline comments", t, func() { - cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, []byte(`key1=value ;comment -key2=value #comment2`)) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(cfg.Section("").Key("key1").String(), ShouldEqual, `value ;comment`) - So(cfg.Section("").Key("key2").String(), ShouldEqual, `value #comment2`) - - var buf bytes.Buffer - cfg.WriteTo(&buf) - So(buf.String(), ShouldEqual, `key1 = value ;comment -key2 = value #comment2 - -`) - }) - - Convey("Load with boolean type keys", t, func() { - cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, []byte(`key1=hello -key2 -#key3 -key4 -key5`)) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(strings.Join(cfg.Section("").KeyStrings(), ","), ShouldEqual, "key1,key2,key4,key5") - So(cfg.Section("").Key("key2").MustBool(false), ShouldBeTrue) - - var buf bytes.Buffer - cfg.WriteTo(&buf) - // there is always a trailing \n at the end of the section - So(buf.String(), ShouldEqual, `key1 = hello -key2 -# key3 -key4 -key5 -`) - }) -} - -func Test_File_ChildSections(t *testing.T) { - Convey("Find child sections by parent name", t, func() { - cfg, err := Load([]byte(` -[node] - -[node.biz1] - -[node.biz2] - -[node.biz3] - -[node.bizN] -`)) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - children := cfg.ChildSections("node") - names := make([]string, len(children)) - for i := range children { - names[i] = children[i].name - } - So(strings.Join(names, ","), ShouldEqual, "node.biz1,node.biz2,node.biz3,node.bizN") - }) -} - -func Test_LooseLoad(t *testing.T) { - Convey("Loose load from data sources", t, func() { - Convey("Loose load mixed with nonexistent file", func() { - cfg, err := LooseLoad("testdata/404.ini") - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - var fake struct { - Name string `ini:"name"` - } - So(cfg.MapTo(&fake), ShouldBeNil) - - cfg, err = LooseLoad([]byte("name=Unknwon"), "testdata/404.ini") - So(err, ShouldBeNil) - So(cfg.Section("").Key("name").String(), ShouldEqual, "Unknwon") - So(cfg.MapTo(&fake), ShouldBeNil) - So(fake.Name, ShouldEqual, "Unknwon") - }) - }) - -} - -func Test_LoadOptions_UnescapeValueDoubleQuotes(t *testing.T) { - Convey("Load with option UnescapeValueDoubleQuotes enabled", t, func() { - cfg, err := LoadSources(LoadOptions{UnescapeValueDoubleQuotes: true}, - []byte(`create_repo="创建了仓库 %s"`)) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(cfg.Section("").Key("create_repo").String(), ShouldEqual, `创建了仓库 %s`) - }) -} - -func Test_File_Append(t *testing.T) { - Convey("Append data sources", t, func() { - cfg, err := Load([]byte("")) - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - So(cfg.Append([]byte(""), []byte("")), ShouldBeNil) - - Convey("Append bad data sources", func() { - So(cfg.Append(1), ShouldNotBeNil) - So(cfg.Append([]byte(""), 1), ShouldNotBeNil) - }) - }) -} - -func Test_File_WriteTo(t *testing.T) { - Convey("Write to somewhere", t, func() { - var buf bytes.Buffer - cfg := Empty() - cfg.WriteTo(&buf) - }) -} - -func Test_File_SaveTo_WriteTo(t *testing.T) { - Convey("Save file", t, func() { - cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini") - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - - cfg.Section("").Key("NAME").Comment = "Package name" - cfg.Section("author").Comment = `Information about package author -# Bio can be written in multiple lines.` - cfg.Section("advanced").Key("val w/ pound").SetValue("my#password") - cfg.Section("advanced").Key("longest key has a colon : yes/no").SetValue("yes") - So(cfg.SaveTo("testdata/conf_out.ini"), ShouldBeNil) - - cfg.Section("author").Key("NAME").Comment = "This is author name" - - So(cfg.SaveToIndent("testdata/conf_out.ini", "\t"), ShouldBeNil) - - var buf bytes.Buffer - _, err = cfg.WriteToIndent(&buf, "\t") - 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 +const ( + _CONF_DATA = ` + ; Package name + NAME = ini + ; Package version + VERSION = v1 + ; Package import path + IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s + + # Information about package author + # Bio can be written in multiple lines. + [author] + NAME = Unknwon ; Succeeding comment + E-MAIL = fake@localhost GITHUB = https://github.com/%(NAME)s - # Succeeding comment BIO = """Gopher. -Coding addict. -Good man. -""" + Coding addict. + Good man. + """ # Succeeding comment` + _MINIMAL_CONF = "testdata/minimal.ini" + _FULL_CONF = "testdata/full.ini" + _NOT_FOUND_CONF = "testdata/404.ini" +) -[package] - CLONE_URL = https://%(IMPORT_PATH)s +func TestLoad(t *testing.T) { + Convey("Load from good data sources", t, func() { + f, err := ini.Load([]byte(` +NAME = ini +VERSION = v1 +IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s`), + "testdata/minimal.ini", + ioutil.NopCloser(bytes.NewReader([]byte(` +[author] +NAME = Unknwon +`))), + ) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) -[package.sub] - UNUSED_KEY = should be deleted + // Vaildate 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") -[features] - - = Support read/write comments of keys and sections - - = Support auto-increment of key names - - = Support load multiple files to overwrite key values + sec = f.Section("author") + So(sec.Key("NAME").String(), ShouldEqual, "Unknwon") + So(sec.Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") + }) -[types] - STRING = str - BOOL = true - BOOL_FALSE = false - FLOAT64 = 1.25 - INT = 10 - TIME = 2015-01-01T20:17:05Z - DURATION = 2h45m - UINT = 3 + Convey("Load from bad data sources", t, func() { + Convey("Invalid input", func() { + _, err := ini.Load(_NOT_FOUND_CONF) + So(err, ShouldNotBeNil) + }) -[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 - -; 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 - -[advanced] - val w/ pound = `+"`"+`my#password`+"`"+` - `+"`"+`longest key has a colon : yes/no`+"`"+` = yes - -`) + Convey("Unsupported type", func() { + _, err := ini.Load(123) + So(err, ShouldNotBeNil) + }) }) } -func Test_File_WriteTo_SectionRaw(t *testing.T) { - Convey("Write a INI with a raw section", t, func() { - var buf bytes.Buffer - cfg, err := LoadSources( - LoadOptions{ - UnparseableSections: []string{"CORE_LESSON", "COMMENTS"}, - }, - "testdata/aicc.ini") - So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) - cfg.WriteToIndent(&buf, "\t") - So(buf.String(), ShouldEqual, `[Core] - Lesson_Location = 87 - Lesson_Status = C - Score = 3 - Time = 00:02:30 +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) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + 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) + 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(_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(` +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.Load([]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{ + IgnoreInlineComment: true, + }, []byte(` +key1=value ;comment +key2=value2 #comment2`)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + So(f.Section("").Key("key1").String(), ShouldEqual, `value ;comment`) + So(f.Section("").Key("key2").String(), ShouldEqual, `value2 #comment2`) + + Convey("Inverse case", func() { + f, err := ini.Load([]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{ + 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.Load([]byte(` +key1=hello +#key2 +key3`)) + So(err, ShouldNotBeNil) + }) + }) + + Convey("Allow shadow keys", func() { + f, err := ini.ShadowLoad([]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.Load([]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{ + 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.Load([]byte(` +create_repo="创建了仓库 %s"`)) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + So(f.Section("").Key("create_repo").String(), ShouldEqual, `"创建了仓库 %s"`) + }) + }) + + Convey("Allow unparseable sections", func() { + f, err := ini.LoadSources(ini.LoadOptions{ + 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.Load([]byte(` +[CORE_LESSON] +my lesson state data – 1111111111111111111000000000000000001110000 +111111111111111111100000000000111000000000 – end my lesson state data`)) + So(err, ShouldNotBeNil) + }) + }) }) } - -// Helpers for slice tests. -func float64sEqual(values []float64, expected ...float64) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i], ShouldEqual, v) - } -} - -func intsEqual(values []int, expected ...int) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i], ShouldEqual, v) - } -} - -func int64sEqual(values []int64, expected ...int64) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i], ShouldEqual, v) - } -} - -func uintsEqual(values []uint, expected ...uint) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i], ShouldEqual, v) - } -} - -func uint64sEqual(values []uint64, expected ...uint64) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i], ShouldEqual, v) - } -} - -func timesEqual(values []time.Time, expected ...time.Time) { - So(values, ShouldHaveLength, len(expected)) - for i, v := range expected { - So(values[i].String(), ShouldEqual, v.String()) - } -} diff --git a/key.go b/key.go index 0a2aa30..ab566c2 100644 --- a/key.go +++ b/key.go @@ -26,6 +26,7 @@ import ( // Key represents a key under a section. type Key struct { s *Section + Comment string name string value string isAutoIncrement bool @@ -33,8 +34,6 @@ type Key struct { isShadow bool shadows []*Key - - Comment string } // newKey simply return a key object with given values. diff --git a/key_test.go b/key_test.go index a70ee64..588efd4 100644 --- a/key_test.go +++ b/key_test.go @@ -12,26 +12,108 @@ // License for the specific language governing permissions and limitations // under the License. -package ini +package ini_test import ( - "bytes" "fmt" "strings" "testing" "time" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) -func Test_Key(t *testing.T) { - Convey("Test getting and setting values", t, func() { - cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini") +func TestKey_AddShadow(t *testing.T) { + Convey("Add shadow to a key", t, func() { + f, err := ini.ShadowLoad([]byte(` +[notes] +-: note1`)) So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) - Convey("Get values in default section", func() { - sec := cfg.Section("") + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(k.AddShadow("ini.v1"), ShouldBeNil) + So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"}) + + Convey("Add shadow to boolean key", func() { + k, err := f.Section("").NewBooleanKey("published") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + So(k.AddShadow("beta"), ShouldNotBeNil) + }) + + Convey("Add shadow to auto-increment key", func() { + So(f.Section("notes").Key("#1").AddShadow("beta"), ShouldNotBeNil) + }) + }) + + Convey("Shadow is not allowed", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(k.AddShadow("ini.v1"), ShouldNotBeNil) + }) +} + +// Helpers for slice tests. +func float64sEqual(values []float64, expected ...float64) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i], ShouldEqual, v) + } +} + +func intsEqual(values []int, expected ...int) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i], ShouldEqual, v) + } +} + +func int64sEqual(values []int64, expected ...int64) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i], ShouldEqual, v) + } +} + +func uintsEqual(values []uint, expected ...uint) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i], ShouldEqual, v) + } +} + +func uint64sEqual(values []uint64, expected ...uint64) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i], ShouldEqual, v) + } +} + +func timesEqual(values []time.Time, expected ...time.Time) { + So(values, ShouldHaveLength, len(expected)) + for i, v := range expected { + So(values[i].String(), ShouldEqual, v.String()) + } +} + +func TestKey_Helpers(t *testing.T) { + Convey("Getting and setting values", t, func() { + f, err := ini.Load(_FULL_CONF) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + Convey("Get string representation", func() { + sec := f.Section("") So(sec, ShouldNotBeNil) So(sec.Key("NAME").Value(), ShouldEqual, "ini") So(sec.Key("NAME").String(), ShouldEqual, "ini") @@ -40,55 +122,65 @@ func Test_Key(t *testing.T) { }), ShouldEqual, "ini") So(sec.Key("NAME").Comment, ShouldEqual, "; Package name") So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1") + + Convey("With ValueMapper", func() { + f.ValueMapper = func(in string) string { + if in == "gopkg.in/%(NAME)s.%(VERSION)s" { + return "github.com/go-ini/ini" + } + return in + } + So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "github.com/go-ini/ini") + }) }) Convey("Get values in non-default section", func() { - sec := cfg.Section("author") + sec := f.Section("author") So(sec, ShouldNotBeNil) So(sec.Key("NAME").String(), ShouldEqual, "Unknwon") So(sec.Key("GITHUB").String(), ShouldEqual, "https://github.com/Unknwon") - sec = cfg.Section("package") + sec = f.Section("package") So(sec, ShouldNotBeNil) So(sec.Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") }) Convey("Get auto-increment key names", func() { - keys := cfg.Section("features").Keys() + keys := f.Section("features").Keys() for i, k := range keys { So(k.Name(), ShouldEqual, fmt.Sprintf("#%d", i+1)) } }) Convey("Get parent-keys that are available to the child section", func() { - parentKeys := cfg.Section("package.sub").ParentKeys() + parentKeys := f.Section("package.sub").ParentKeys() for _, k := range parentKeys { So(k.Name(), ShouldEqual, "CLONE_URL") } }) Convey("Get overwrite value", func() { - So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") + So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") }) Convey("Get sections", func() { - sections := cfg.Sections() - for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} { + sections := f.Sections() + for i, name := range []string{ini.DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "string escapes", "advance"} { So(sections[i].Name(), ShouldEqual, name) } }) Convey("Get parent section value", func() { - So(cfg.Section("package.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") - So(cfg.Section("package.fake.sub").Key("CLONE_URL").String(), ShouldEqual, "https://gopkg.in/ini.v1") + 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 multiple line value", func() { - So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n") + So(f.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n") }) Convey("Get values with type", func() { - sec := cfg.Section("types") + sec := f.Section("types") v1, err := sec.Key("BOOL").Bool() So(err, ShouldBeNil) So(v1, ShouldBeTrue) @@ -168,7 +260,7 @@ func Test_Key(t *testing.T) { }) Convey("Get value with candidates", func() { - sec := cfg.Section("types") + sec := f.Section("types") So(sec.Key("STRING").In("", []string{"str", "arr", "types"}), ShouldEqual, "str") So(sec.Key("FLOAT64").InFloat64(0, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25) So(sec.Key("INT").InInt(0, []int{10, 20, 30}), ShouldEqual, 10) @@ -194,7 +286,7 @@ func Test_Key(t *testing.T) { }) Convey("Get values in range", func() { - sec := cfg.Section("types") + sec := f.Section("types") So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25) So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10) So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10) @@ -218,7 +310,7 @@ func Test_Key(t *testing.T) { }) Convey("Get values into slice", func() { - sec := cfg.Section("array") + sec := f.Section("array") So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de") So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0) @@ -244,7 +336,7 @@ func Test_Key(t *testing.T) { }) Convey("Test string slice escapes", func() { - sec := cfg.Section("string escapes") + sec := f.Section("string escapes") So(sec.Key("key1").Strings(","), ShouldResemble, []string{"value1", "value2", "value3"}) So(sec.Key("key2").Strings(","), ShouldResemble, []string{"value1, value2"}) So(sec.Key("key3").Strings(","), ShouldResemble, []string{`val\ue1`, "value2"}) @@ -254,7 +346,7 @@ func Test_Key(t *testing.T) { }) Convey("Get valid values into slice", func() { - sec := cfg.Section("array") + sec := f.Section("array") vals1 := sec.Key("FLOAT64S").ValidFloat64s(",") float64sEqual(vals1, 1.1, 2.2, 3.3) @@ -277,7 +369,7 @@ func Test_Key(t *testing.T) { }) Convey("Get values one type into slice of another type", func() { - sec := cfg.Section("array") + sec := f.Section("array") vals1 := sec.Key("STRINGS").ValidFloat64s(",") So(vals1, ShouldBeEmpty) @@ -298,7 +390,7 @@ func Test_Key(t *testing.T) { }) Convey("Get valid values into slice without errors", func() { - sec := cfg.Section("array") + sec := f.Section("array") vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",") So(err, ShouldBeNil) float64sEqual(vals1, 1.1, 2.2, 3.3) @@ -327,7 +419,7 @@ func Test_Key(t *testing.T) { }) Convey("Get invalid values into slice", func() { - sec := cfg.Section("array") + sec := f.Section("array") vals1, err := sec.Key("STRINGS").StrictFloat64s(",") So(vals1, ShouldBeEmpty) So(err, ShouldNotBeNil) @@ -352,232 +444,37 @@ func Test_Key(t *testing.T) { So(vals6, ShouldBeEmpty) So(err, ShouldNotBeNil) }) - - Convey("Get key hash", func() { - cfg.Section("").KeysHash() - }) - - Convey("Set key value", func() { - k := cfg.Section("author").Key("NAME") - k.SetValue("无闻") - So(k.String(), ShouldEqual, "无闻") - }) - - Convey("Get key strings", func() { - So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME,DURATION,UINT") - }) - - Convey("Delete a key", func() { - cfg.Section("package.sub").DeleteKey("UNUSED_KEY") - _, err := cfg.Section("package.sub").GetKey("UNUSED_KEY") - So(err, ShouldNotBeNil) - }) - - Convey("Has Key (backwards compatible)", func() { - sec := cfg.Section("package.sub") - haskey1 := sec.Haskey("UNUSED_KEY") - haskey2 := sec.Haskey("CLONE_URL") - haskey3 := sec.Haskey("CLONE_URL_NO") - So(haskey1, ShouldBeTrue) - So(haskey2, ShouldBeTrue) - So(haskey3, ShouldBeFalse) - }) - - Convey("Has Key", func() { - sec := cfg.Section("package.sub") - haskey1 := sec.HasKey("UNUSED_KEY") - haskey2 := sec.HasKey("CLONE_URL") - haskey3 := sec.HasKey("CLONE_URL_NO") - So(haskey1, ShouldBeTrue) - So(haskey2, ShouldBeTrue) - So(haskey3, ShouldBeFalse) - }) - - Convey("Has Value", func() { - sec := cfg.Section("author") - hasvalue1 := sec.HasValue("Unknwon") - hasvalue2 := sec.HasValue("doc") - So(hasvalue1, ShouldBeTrue) - So(hasvalue2, ShouldBeFalse) - }) }) +} - Convey("Test getting and setting bad values", t, func() { - cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini") +func TestKey_StringsWithShadows(t *testing.T) { + Convey("Get strings of shadows of a key", t, func() { + f, err := ini.ShadowLoad([]byte("")) So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) - Convey("Create new key with empty name", func() { - k, err := cfg.Section("").NewKey("", "") - So(err, ShouldNotBeNil) - So(k, ShouldBeNil) - }) - - Convey("Create new section with empty name", func() { - s, err := cfg.NewSection("") - So(err, ShouldNotBeNil) - So(s, ShouldBeNil) - }) - - Convey("Create new sections with empty name", func() { - So(cfg.NewSections(""), ShouldNotBeNil) - }) - - Convey("Get section that not exists", func() { - s, err := cfg.GetSection("404") - So(err, ShouldNotBeNil) - So(s, ShouldBeNil) - - s = cfg.Section("404") - So(s, ShouldNotBeNil) - }) - }) - - Convey("Test key hash clone", t, func() { - cfg, err := Load([]byte(strings.Replace("network=tcp,addr=127.0.0.1:6379,db=4,pool_size=100,idle_timeout=180", ",", "\n", -1))) + k, err := f.Section("").NewKey("NUMS", "1,2") So(err, ShouldBeNil) - for _, v := range cfg.Section("").KeysHash() { - So(len(v), ShouldBeGreaterThan, 0) - } - }) - - Convey("Key has empty value", t, func() { - _conf := `key1= -key2= ; comment` - cfg, err := Load([]byte(_conf)) + So(k, ShouldNotBeNil) + k, err = f.Section("").NewKey("NUMS", "4,5,6") So(err, ShouldBeNil) - So(cfg.Section("").Key("key1").Value(), ShouldBeEmpty) + So(k, ShouldNotBeNil) + + So(k.StringsWithShadows(","), ShouldResemble, []string{"1", "2", "4", "5", "6"}) }) } -const _CONF_GIT_CONFIG = ` -[remote "origin"] - url = https://github.com/Antergone/test1.git - url = https://github.com/Antergone/test2.git -` +func TestKey_SetValue(t *testing.T) { + Convey("Set value of key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) -func Test_Key_Shadows(t *testing.T) { - Convey("Shadows keys", t, func() { - Convey("Disable shadows", func() { - cfg, err := Load([]byte(_CONF_GIT_CONFIG)) - So(err, ShouldBeNil) - So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test2.git") - }) + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + So(k.Value(), ShouldEqual, "ini") - Convey("Enable shadows", func() { - cfg, err := ShadowLoad([]byte(_CONF_GIT_CONFIG)) - So(err, ShouldBeNil) - So(cfg.Section(`remote "origin"`).Key("url").String(), ShouldEqual, "https://github.com/Antergone/test1.git") - So(strings.Join(cfg.Section(`remote "origin"`).Key("url").ValueWithShadows(), " "), ShouldEqual, - "https://github.com/Antergone/test1.git https://github.com/Antergone/test2.git") - - Convey("Save with shadows", func() { - var buf bytes.Buffer - _, err := cfg.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 - -`) - }) - }) + k.SetValue("ini.v1") + So(k.Value(), ShouldEqual, "ini.v1") }) } - -func newTestFile(block bool) *File { - c, _ := Load([]byte(_CONF_DATA)) - c.BlockMode = block - return c -} - -func Benchmark_Key_Value(b *testing.B) { - c := newTestFile(true) - for i := 0; i < b.N; i++ { - c.Section("").Key("NAME").Value() - } -} - -func Benchmark_Key_Value_NonBlock(b *testing.B) { - c := newTestFile(false) - for i := 0; i < b.N; i++ { - c.Section("").Key("NAME").Value() - } -} - -func Benchmark_Key_Value_ViaSection(b *testing.B) { - c := newTestFile(true) - sec := c.Section("") - for i := 0; i < b.N; i++ { - sec.Key("NAME").Value() - } -} - -func Benchmark_Key_Value_ViaSection_NonBlock(b *testing.B) { - c := newTestFile(false) - sec := c.Section("") - for i := 0; i < b.N; i++ { - sec.Key("NAME").Value() - } -} - -func Benchmark_Key_Value_Direct(b *testing.B) { - c := newTestFile(true) - key := c.Section("").Key("NAME") - for i := 0; i < b.N; i++ { - key.Value() - } -} - -func Benchmark_Key_Value_Direct_NonBlock(b *testing.B) { - c := newTestFile(false) - key := c.Section("").Key("NAME") - for i := 0; i < b.N; i++ { - key.Value() - } -} - -func Benchmark_Key_String(b *testing.B) { - c := newTestFile(true) - for i := 0; i < b.N; i++ { - _ = c.Section("").Key("NAME").String() - } -} - -func Benchmark_Key_String_NonBlock(b *testing.B) { - c := newTestFile(false) - for i := 0; i < b.N; i++ { - _ = c.Section("").Key("NAME").String() - } -} - -func Benchmark_Key_String_ViaSection(b *testing.B) { - c := newTestFile(true) - sec := c.Section("") - for i := 0; i < b.N; i++ { - _ = sec.Key("NAME").String() - } -} - -func Benchmark_Key_String_ViaSection_NonBlock(b *testing.B) { - c := newTestFile(false) - sec := c.Section("") - for i := 0; i < b.N; i++ { - _ = sec.Key("NAME").String() - } -} - -func Benchmark_Key_SetValue(b *testing.B) { - c := newTestFile(true) - for i := 0; i < b.N; i++ { - c.Section("").Key("NAME").SetValue("10") - } -} - -func Benchmark_Key_SetValue_VisSection(b *testing.B) { - c := newTestFile(true) - sec := c.Section("") - for i := 0; i < b.N; i++ { - sec.Key("NAME").SetValue("10") - } -} diff --git a/parser_test.go b/parser_test.go index 0525819..bb0f266 100644 --- a/parser_test.go +++ b/parser_test.go @@ -12,31 +12,66 @@ // License for the specific language governing permissions and limitations // under the License. -package ini +package ini_test import ( "testing" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) -func Test_BOM(t *testing.T) { +func TestBOM(t *testing.T) { Convey("Test handling BOM", t, func() { Convey("UTF-8-BOM", func() { - cfg, err := Load("testdata/UTF-8-BOM.ini") + f, err := ini.Load("testdata/UTF-8-BOM.ini") So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) - So(cfg.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") + So(f.Section("author").Key("E-MAIL").String(), ShouldEqual, "u@gogs.io") }) Convey("UTF-16-LE-BOM", func() { - cfg, err := Load("testdata/UTF-16-LE-BOM.ini") + f, err := ini.Load("testdata/UTF-16-LE-BOM.ini") So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) }) Convey("UTF-16-BE-BOM", func() { }) }) } + +func TestBadLoad(t *testing.T) { + Convey("Load with bad data", t, func() { + Convey("Bad section name", func() { + _, err := ini.Load([]byte("[]")) + So(err, ShouldNotBeNil) + + _, err = ini.Load([]byte("[")) + So(err, ShouldNotBeNil) + }) + + Convey("Bad keys", func() { + _, err := ini.Load([]byte(`"""name`)) + So(err, ShouldNotBeNil) + + _, err = ini.Load([]byte(`"""name"""`)) + So(err, ShouldNotBeNil) + + _, err = ini.Load([]byte(`""=1`)) + So(err, ShouldNotBeNil) + + _, err = ini.Load([]byte(`=`)) + So(err, ShouldNotBeNil) + + _, err = ini.Load([]byte(`name`)) + So(err, ShouldNotBeNil) + }) + + Convey("Bad values", func() { + _, err := ini.Load([]byte(`name="""Unknwon`)) + So(err, ShouldNotBeNil) + }) + }) +} diff --git a/section.go b/section.go index 94f7375..d8a4026 100644 --- a/section.go +++ b/section.go @@ -54,6 +54,14 @@ func (s *Section) Body() string { return strings.TrimSpace(s.rawBody) } +// SetBody updates body content only if section is raw. +func (s *Section) SetBody(body string) { + if !s.isRawSection { + return + } + s.rawBody = body +} + // NewKey creates a new key to given section. func (s *Section) NewKey(name, val string) (*Key, error) { if len(name) == 0 { @@ -136,6 +144,7 @@ func (s *Section) HasKey(name string) bool { } // Haskey is a backwards-compatible name for HasKey. +// TODO: delete me in v2 func (s *Section) Haskey(name string) bool { return s.HasKey(name) } diff --git a/section_test.go b/section_test.go index f1ea671..e9c3478 100644 --- a/section_test.go +++ b/section_test.go @@ -12,64 +12,302 @@ // License for the specific language governing permissions and limitations // under the License. -package ini +package ini_test import ( - "strings" "testing" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) -func Test_Section(t *testing.T) { - Convey("Test CRD sections", t, func() { - cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini") +func TestSection_SetBody(t *testing.T) { + Convey("Set body of raw section", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + sec, err := f.NewRawSection("comments", `1111111111111111111000000000000000001110000 +111111111111111111100000000000111000000000`) So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(sec, ShouldNotBeNil) + So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000 +111111111111111111100000000000111000000000`) - Convey("Get section strings", func() { - So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,comments,string escapes,advance") - }) + sec.SetBody("1111111111111111111000000000000000001110000") + So(sec.Body(), ShouldEqual, `1111111111111111111000000000000000001110000`) - Convey("Delete a section", func() { - cfg.DeleteSection("") - So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION) - }) - - Convey("Create new sections", func() { - cfg.NewSections("test", "test2") - _, err := cfg.GetSection("test") - So(err, ShouldBeNil) - _, err = cfg.GetSection("test2") + Convey("Set for non-raw section", func() { + sec, err := f.NewSection("author") So(err, ShouldBeNil) + So(sec, ShouldNotBeNil) + So(sec.Body(), ShouldBeEmpty) + + sec.SetBody("1111111111111111111000000000000000001110000") + So(sec.Body(), ShouldBeEmpty) }) }) } -func Test_SectionRaw(t *testing.T) { - Convey("Test section raw string", t, func() { - cfg, err := LoadSources( - LoadOptions{ - Insensitive: true, - UnparseableSections: []string{"core_lesson", "comments"}, - }, - "testdata/aicc.ini") +func TestSection_NewKey(t *testing.T) { + Convey("Create a new key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(k, ShouldNotBeNil) + So(k.Name(), ShouldEqual, "NAME") + So(k.Value(), ShouldEqual, "ini") - Convey("Get section strings", func() { - So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,core,core_lesson,comments") - }) - - Convey("Validate non-raw section", func() { - val, err := cfg.Section("core").GetKey("lesson_status") + Convey("With duplicated name", func() { + k, err := f.Section("").NewKey("NAME", "ini.v1") So(err, ShouldBeNil) - So(val.String(), ShouldEqual, "C") + So(k, ShouldNotBeNil) + + // Overwrite previous existed key + So(k.Value(), ShouldEqual, "ini.v1") }) - Convey("Validate raw section", func() { - So(cfg.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000 -111111111111111111100000000000111000000000 – end my lesson state data`) + Convey("With empty string", func() { + _, err := f.Section("").NewKey("", "") + So(err, ShouldNotBeNil) + }) + }) + + Convey("Create keys with same name and allow shadow", t, func() { + f, err := ini.ShadowLoad([]byte("")) + So(err, ShouldBeNil) + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + k, err = f.Section("").NewKey("NAME", "ini.v1") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(k.ValueWithShadows(), ShouldResemble, []string{"ini", "ini.v1"}) + }) +} + +func TestSection_NewBooleanKey(t *testing.T) { + Convey("Create a new boolean key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewBooleanKey("start-ssh-server") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + So(k.Name(), ShouldEqual, "start-ssh-server") + So(k.Value(), ShouldEqual, "true") + + Convey("With empty string", func() { + _, err := f.Section("").NewBooleanKey("") + So(err, ShouldNotBeNil) }) }) } + +func TestSection_GetKey(t *testing.T) { + Convey("Get a key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + k, err = f.Section("").GetKey("NAME") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + So(k.Name(), ShouldEqual, "NAME") + So(k.Value(), ShouldEqual, "ini") + + Convey("Key not exists", func() { + _, err := f.Section("").GetKey("404") + So(err, ShouldNotBeNil) + }) + + Convey("Key exists in parent section", func() { + k, err := f.Section("parent").NewKey("AGE", "18") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + k, err = f.Section("parent.child.son").GetKey("AGE") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + So(k.Value(), ShouldEqual, "18") + }) + }) +} + +func TestSection_HasKey(t *testing.T) { + Convey("Check if a key exists", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(f.Section("").HasKey("NAME"), ShouldBeTrue) + So(f.Section("").Haskey("NAME"), ShouldBeTrue) + So(f.Section("").HasKey("404"), ShouldBeFalse) + So(f.Section("").Haskey("404"), ShouldBeFalse) + }) +} + +func TestSection_HasValue(t *testing.T) { + Convey("Check if contains a value in any key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(f.Section("").HasValue("ini"), ShouldBeTrue) + So(f.Section("").HasValue("404"), ShouldBeFalse) + }) +} + +func TestSection_Key(t *testing.T) { + Convey("Get a key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + k = f.Section("").Key("NAME") + So(k, ShouldNotBeNil) + So(k.Name(), ShouldEqual, "NAME") + So(k.Value(), ShouldEqual, "ini") + + Convey("Key not exists", func() { + k := f.Section("").Key("404") + So(k, ShouldNotBeNil) + So(k.Name(), ShouldEqual, "404") + }) + + Convey("Key exists in parent section", func() { + k, err := f.Section("parent").NewKey("AGE", "18") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + k = f.Section("parent.child.son").Key("AGE") + So(k, ShouldNotBeNil) + So(k.Value(), ShouldEqual, "18") + }) + }) +} + +func TestSection_Keys(t *testing.T) { + Convey("Get all keys in a section", t, func() { + f := ini.Empty() + 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) + + keys := f.Section("").Keys() + names := []string{"NAME", "VERSION", "IMPORT_PATH"} + So(len(keys), ShouldEqual, len(names)) + for i, name := range names { + So(keys[i].Name(), ShouldEqual, name) + } + }) +} + +func TestSection_ParentKeys(t *testing.T) { + Convey("Get all keys of parent sections", t, func() { + f := ini.Empty() + 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) + } + }) +} + +func TestSection_KeyStrings(t *testing.T) { + Convey("Get all key names in a section", t, func() { + f := ini.Empty() + 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("").KeyStrings(), ShouldResemble, []string{"NAME", "VERSION", "IMPORT_PATH"}) + }) +} + +func TestSection_KeyHash(t *testing.T) { + Convey("Get clone of key hash", t, func() { + f := ini.Empty() + 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) + + hash := f.Section("").KeysHash() + relation := map[string]string{ + "NAME": "ini", + "VERSION": "v1", + "IMPORT_PATH": "gopkg.in/ini.v1", + } + for k, v := range hash { + So(v, ShouldEqual, relation[k]) + } + }) +} + +func TestSection_DeleteKey(t *testing.T) { + Convey("Delete a key", t, func() { + f := ini.Empty() + So(f, ShouldNotBeNil) + + k, err := f.Section("").NewKey("NAME", "ini") + So(err, ShouldBeNil) + So(k, ShouldNotBeNil) + + So(f.Section("").HasKey("NAME"), ShouldBeTrue) + f.Section("").DeleteKey("NAME") + So(f.Section("").HasKey("NAME"), ShouldBeFalse) + }) +} diff --git a/struct_test.go b/struct_test.go index 86bb9de..8492e93 100644 --- a/struct_test.go +++ b/struct_test.go @@ -12,7 +12,7 @@ // License for the specific language governing permissions and limitations // under the License. -package ini +package ini_test import ( "bytes" @@ -22,6 +22,7 @@ import ( "time" . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" ) type testNested struct { @@ -126,11 +127,11 @@ Born = nil Cities = ` -func Test_Struct(t *testing.T) { +func Test_MapToStruct(t *testing.T) { Convey("Map to struct", t, func() { Convey("Map file to struct", func() { ts := new(testStruct) - So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) + So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) So(ts.Name, ShouldEqual, "Unknwon") So(ts.Age, ShouldEqual, 21) @@ -159,7 +160,7 @@ func Test_Struct(t *testing.T) { Convey("Map section to struct", func() { foobar := new(fooBar) - f, err := Load([]byte(_CONF_DATA_STRUCT)) + f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) So(err, ShouldBeNil) So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil) @@ -168,58 +169,58 @@ func Test_Struct(t *testing.T) { }) Convey("Map to non-pointer struct", func() { - cfg, err := Load([]byte(_CONF_DATA_STRUCT)) + f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) - So(cfg.MapTo(testStruct{}), ShouldNotBeNil) + So(f.MapTo(testStruct{}), ShouldNotBeNil) }) Convey("Map to unsupported type", func() { - cfg, err := Load([]byte(_CONF_DATA_STRUCT)) + f, err := ini.Load([]byte(_CONF_DATA_STRUCT)) So(err, ShouldBeNil) - So(cfg, ShouldNotBeNil) + So(f, ShouldNotBeNil) - cfg.NameMapper = func(raw string) string { + f.NameMapper = func(raw string) string { if raw == "Byte" { return "NAME" } return raw } - So(cfg.MapTo(&unsupport{}), ShouldNotBeNil) - So(cfg.MapTo(&unsupport2{}), ShouldNotBeNil) - So(cfg.MapTo(&unsupport4{}), ShouldNotBeNil) + So(f.MapTo(&unsupport{}), ShouldNotBeNil) + So(f.MapTo(&unsupport2{}), ShouldNotBeNil) + So(f.MapTo(&unsupport4{}), ShouldNotBeNil) }) Convey("Map to omitempty field", func() { ts := new(testStruct) - So(MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) + So(ini.MapTo(ts, []byte(_CONF_DATA_STRUCT)), ShouldBeNil) So(ts.Omitted, ShouldEqual, true) }) Convey("Map with shadows", func() { - cfg, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT)) + f, err := ini.LoadSources(ini.LoadOptions{AllowShadows: true}, []byte(_CONF_DATA_STRUCT)) So(err, ShouldBeNil) ts := new(testStruct) - So(cfg.MapTo(ts), ShouldBeNil) + So(f.MapTo(ts), ShouldBeNil) So(strings.Join(ts.Shadows, " "), ShouldEqual, "1 2 3 4") So(fmt.Sprintf("%v", ts.ShadowInts), ShouldEqual, "[1 2 3 4]") }) Convey("Map from invalid data source", func() { - So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil) + So(ini.MapTo(&testStruct{}, "hi"), ShouldNotBeNil) }) Convey("Map to wrong types and gain default values", func() { - cfg, err := Load([]byte(_INVALID_DATA_CONF_STRUCT)) + f, err := ini.Load([]byte(_INVALID_DATA_CONF_STRUCT)) So(err, ShouldBeNil) t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z") So(err, ShouldBeNil) dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}} - So(cfg.MapTo(dv), ShouldBeNil) + So(f.MapTo(dv), ShouldBeNil) So(dv.Name, ShouldEqual, "Joe") So(dv.Age, ShouldEqual, 10) So(dv.Male, ShouldBeTrue) @@ -230,7 +231,7 @@ func Test_Struct(t *testing.T) { }) Convey("Map to struct in strict mode", t, func() { - cfg, err := Load([]byte(` + f, err := ini.Load([]byte(` name=bruce age=a30`)) So(err, ShouldBeNil) @@ -241,11 +242,11 @@ age=a30`)) } s := new(Strict) - So(cfg.Section("").StrictMapTo(s), ShouldNotBeNil) + So(f.Section("").StrictMapTo(s), ShouldNotBeNil) }) Convey("Map slice in strict mode", t, func() { - cfg, err := Load([]byte(` + f, err := ini.Load([]byte(` names=alice, bruce`)) So(err, ShouldBeNil) @@ -254,10 +255,12 @@ names=alice, bruce`)) } s := new(Strict) - So(cfg.Section("").StrictMapTo(s), ShouldBeNil) + So(f.Section("").StrictMapTo(s), ShouldBeNil) So(fmt.Sprint(s.Names), ShouldEqual, "[alice bruce]") }) +} +func Test_ReflectFromStruct(t *testing.T) { Convey("Reflect from struct", t, func() { type Embeded struct { Dates []time.Time `delim:"|" comment:"Time data"` @@ -293,8 +296,8 @@ names=alice, bruce`)) []float64{192.168, 10.11}, []int{}, }} - cfg := Empty() - So(ReflectFrom(cfg, a), ShouldBeNil) + cfg := ini.Empty() + So(ini.ReflectFrom(cfg, a), ShouldBeNil) var buf bytes.Buffer _, err = cfg.WriteTo(&buf) @@ -322,11 +325,11 @@ None = `) Convey("Reflect from non-point struct", func() { - So(ReflectFrom(cfg, Author{}), ShouldNotBeNil) + So(ini.ReflectFrom(cfg, Author{}), ShouldNotBeNil) }) Convey("Reflect from struct with omitempty", func() { - cfg := Empty() + cfg := ini.Empty() type SpecialStruct struct { FirstName string `ini:"first_name"` LastName string `ini:"last_name"` @@ -336,7 +339,7 @@ None = NotEmpty int `ini:"omitempty"` } - So(ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil) + So(ini.ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil) var buf bytes.Buffer _, err = cfg.WriteTo(&buf) @@ -355,13 +358,13 @@ type testMapper struct { func Test_NameGetter(t *testing.T) { Convey("Test name mappers", t, func() { - So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil) + So(ini.MapToWithMapper(&testMapper{}, ini.TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil) - cfg, err := Load([]byte("PACKAGE_NAME=ini")) + cfg, err := ini.Load([]byte("PACKAGE_NAME=ini")) So(err, ShouldBeNil) So(cfg, ShouldNotBeNil) - cfg.NameMapper = AllCapsUnderscore + cfg.NameMapper = ini.AllCapsUnderscore tg := new(testMapper) So(cfg.MapTo(tg), ShouldBeNil) So(tg.PackageName, ShouldEqual, "ini") diff --git a/testdata/aicc.ini b/testdata/aicc.ini deleted file mode 100644 index 59a6197..0000000 --- a/testdata/aicc.ini +++ /dev/null @@ -1,11 +0,0 @@ -[Core] - 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 diff --git a/testdata/full.ini b/testdata/full.ini new file mode 100644 index 0000000..469b1f1 --- /dev/null +++ b/testdata/full.ini @@ -0,0 +1,83 @@ +; 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 +E-MAIL = u@gogs.io +GITHUB = https://github.com/%(NAME)s +BIO = """Gopher. +Coding addict. +Good man. +""" # Succeeding comment + +[package] +CLONE_URL = https://%(IMPORT_PATH)s + +[package.sub] +UNUSED_KEY = should be deleted + +[features] +-: Support read/write comments of keys and sections +-: Support auto-increment of key names +-: Support load multiple files to overwrite key values + +[types] +STRING = str +BOOL = true +BOOL_FALSE = false +FLOAT64 = 1.25 +INT = 10 +TIME = 2015-01-01T20:17:05Z +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\ + +; Comment before the section +[comments] ; This is a comment for the section too +; Comment before key +key = "value" +key2 = "value2" ; This is a comment for key2 +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/conf.ini b/testdata/minimal.ini similarity index 100% rename from testdata/conf.ini rename to testdata/minimal.ini