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