Switch to using the dep tool and update all the dependencies
This commit is contained in:
+40
-22
@@ -37,7 +37,7 @@ const (
|
||||
|
||||
// Maximum allowed depth when recursively substituing variable names.
|
||||
_DEPTH_VALUES = 99
|
||||
_VERSION = "1.24.0"
|
||||
_VERSION = "1.27.0"
|
||||
)
|
||||
|
||||
// Version returns current package version literal.
|
||||
@@ -173,9 +173,13 @@ type LoadOptions struct {
|
||||
Insensitive bool
|
||||
// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
|
||||
IgnoreContinuation bool
|
||||
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
|
||||
IgnoreInlineComment bool
|
||||
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
|
||||
// This type of keys are mostly used in my.cnf.
|
||||
AllowBooleanKeys bool
|
||||
// AllowShadows indicates whether to keep track of keys with same name under same section.
|
||||
AllowShadows bool
|
||||
// Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
|
||||
// conform to key/value pairs. Specify the names of those blocks here.
|
||||
UnparseableSections []string
|
||||
@@ -219,6 +223,12 @@ func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{Insensitive: true}, source, others...)
|
||||
}
|
||||
|
||||
// InsensitiveLoad has exactly same functionality as Load function
|
||||
// except it allows have shadow keys.
|
||||
func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
|
||||
return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
|
||||
}
|
||||
|
||||
// Empty returns an empty file object.
|
||||
func Empty() *File {
|
||||
// Ignore error here, we sure our data is good.
|
||||
@@ -311,6 +321,11 @@ func (f *File) Sections() []*Section {
|
||||
return sections
|
||||
}
|
||||
|
||||
// ChildSections returns a list of child sections of given section name.
|
||||
func (f *File) ChildSections(name string) []*Section {
|
||||
return f.Section(name).ChildSections()
|
||||
}
|
||||
|
||||
// SectionStrings returns list of section names.
|
||||
func (f *File) SectionStrings() []string {
|
||||
list := make([]string, len(f.sectionList))
|
||||
@@ -441,6 +456,7 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
|
||||
}
|
||||
alignSpaces := bytes.Repeat([]byte(" "), alignLength)
|
||||
|
||||
KEY_LIST:
|
||||
for _, kname := range sec.keyList {
|
||||
key := sec.Key(kname)
|
||||
if len(key.Comment) > 0 {
|
||||
@@ -467,31 +483,33 @@ func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
|
||||
case strings.Contains(kname, "`"):
|
||||
kname = `"""` + kname + `"""`
|
||||
}
|
||||
if _, err = buf.WriteString(kname); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if key.isBooleanType {
|
||||
if kname != sec.keyList[len(sec.keyList)-1] {
|
||||
buf.WriteString(LineBreak)
|
||||
for _, val := range key.ValueWithShadows() {
|
||||
if _, err = buf.WriteString(kname); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Write out alignment spaces before "=" sign
|
||||
if PrettyFormat {
|
||||
buf.Write(alignSpaces[:alignLength-len(kname)])
|
||||
}
|
||||
if key.isBooleanType {
|
||||
if kname != sec.keyList[len(sec.keyList)-1] {
|
||||
buf.WriteString(LineBreak)
|
||||
}
|
||||
continue KEY_LIST
|
||||
}
|
||||
|
||||
val := key.value
|
||||
// In case key value contains "\n", "`", "\"", "#" or ";"
|
||||
if strings.ContainsAny(val, "\n`") {
|
||||
val = `"""` + val + `"""`
|
||||
} else if strings.ContainsAny(val, "#;") {
|
||||
val = "`" + val + "`"
|
||||
}
|
||||
if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
|
||||
return 0, err
|
||||
// Write out alignment spaces before "=" sign
|
||||
if PrettyFormat {
|
||||
buf.Write(alignSpaces[:alignLength-len(kname)])
|
||||
}
|
||||
|
||||
// In case key value contains "\n", "`", "\"", "#" or ";"
|
||||
if strings.ContainsAny(val, "\n`") {
|
||||
val = `"""` + val + `"""`
|
||||
} else if strings.ContainsAny(val, "#;") {
|
||||
val = "`" + val + "`"
|
||||
}
|
||||
if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+484
@@ -0,0 +1,484 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
[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`)
|
||||
})
|
||||
|
||||
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_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
|
||||
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
|
||||
|
||||
; 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"
|
||||
|
||||
[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
|
||||
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
[CORE_LESSON]
|
||||
my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data
|
||||
[COMMENTS]
|
||||
<1><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
||||
`)
|
||||
})
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
+107
-41
@@ -15,6 +15,7 @@
|
||||
package ini
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -29,9 +30,42 @@ type Key struct {
|
||||
isAutoIncrement bool
|
||||
isBooleanType bool
|
||||
|
||||
isShadow bool
|
||||
shadows []*Key
|
||||
|
||||
Comment string
|
||||
}
|
||||
|
||||
// newKey simply return a key object with given values.
|
||||
func newKey(s *Section, name, val string) *Key {
|
||||
return &Key{
|
||||
s: s,
|
||||
name: name,
|
||||
value: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Key) addShadow(val string) error {
|
||||
if k.isShadow {
|
||||
return errors.New("cannot add shadow to another shadow key")
|
||||
} else if k.isAutoIncrement || k.isBooleanType {
|
||||
return errors.New("cannot add shadow to auto-increment or boolean key")
|
||||
}
|
||||
|
||||
shadow := newKey(k.s, k.name, val)
|
||||
shadow.isShadow = true
|
||||
k.shadows = append(k.shadows, shadow)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddShadow adds a new shadow key to itself.
|
||||
func (k *Key) AddShadow(val string) error {
|
||||
if !k.s.f.options.AllowShadows {
|
||||
return errors.New("shadow key is not allowed")
|
||||
}
|
||||
return k.addShadow(val)
|
||||
}
|
||||
|
||||
// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
|
||||
type ValueMapper func(string) string
|
||||
|
||||
@@ -45,16 +79,29 @@ func (k *Key) Value() string {
|
||||
return k.value
|
||||
}
|
||||
|
||||
// String returns string representation of value.
|
||||
func (k *Key) String() string {
|
||||
val := k.value
|
||||
// ValueWithShadows returns raw values of key and its shadows if any.
|
||||
func (k *Key) ValueWithShadows() []string {
|
||||
if len(k.shadows) == 0 {
|
||||
return []string{k.value}
|
||||
}
|
||||
vals := make([]string, len(k.shadows)+1)
|
||||
vals[0] = k.value
|
||||
for i := range k.shadows {
|
||||
vals[i+1] = k.shadows[i].value
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// transformValue takes a raw value and transforms to its final string.
|
||||
func (k *Key) transformValue(val string) string {
|
||||
if k.s.f.ValueMapper != nil {
|
||||
val = k.s.f.ValueMapper(val)
|
||||
}
|
||||
if strings.Index(val, "%") == -1 {
|
||||
|
||||
// Fail-fast if no indicate char found for recursive value
|
||||
if !strings.Contains(val, "%") {
|
||||
return val
|
||||
}
|
||||
|
||||
for i := 0; i < _DEPTH_VALUES; i++ {
|
||||
vr := varPattern.FindString(val)
|
||||
if len(vr) == 0 {
|
||||
@@ -78,6 +125,11 @@ func (k *Key) String() string {
|
||||
return val
|
||||
}
|
||||
|
||||
// String returns string representation of value.
|
||||
func (k *Key) String() string {
|
||||
return k.transformValue(k.value)
|
||||
}
|
||||
|
||||
// Validate accepts a validate function which can
|
||||
// return modifed result as key value.
|
||||
func (k *Key) Validate(fn func(string) string) string {
|
||||
@@ -394,45 +446,65 @@ func (k *Key) Strings(delim string) []string {
|
||||
|
||||
vals := strings.Split(str, delim)
|
||||
for i := range vals {
|
||||
// vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
|
||||
vals[i] = strings.TrimSpace(vals[i])
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// StringsWithShadows returns list of string divided by given delimiter.
|
||||
// Shadows will also be appended if any.
|
||||
func (k *Key) StringsWithShadows(delim string) []string {
|
||||
vals := k.ValueWithShadows()
|
||||
results := make([]string, 0, len(vals)*2)
|
||||
for i := range vals {
|
||||
if len(vals) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, strings.Split(vals[i], delim)...)
|
||||
}
|
||||
|
||||
for i := range results {
|
||||
results[i] = k.transformValue(strings.TrimSpace(results[i]))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Float64s(delim string) []float64 {
|
||||
vals, _ := k.getFloat64s(delim, true, false)
|
||||
vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Ints(delim string) []int {
|
||||
vals, _ := k.getInts(delim, true, false)
|
||||
vals, _ := k.parseInts(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Int64s(delim string) []int64 {
|
||||
vals, _ := k.getInt64s(delim, true, false)
|
||||
vals, _ := k.parseInt64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Uints(delim string) []uint {
|
||||
vals, _ := k.getUints(delim, true, false)
|
||||
vals, _ := k.parseUints(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
|
||||
func (k *Key) Uint64s(delim string) []uint64 {
|
||||
vals, _ := k.getUint64s(delim, true, false)
|
||||
vals, _ := k.parseUint64s(k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||
// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
|
||||
func (k *Key) TimesFormat(format, delim string) []time.Time {
|
||||
vals, _ := k.getTimesFormat(format, delim, true, false)
|
||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
@@ -445,41 +517,41 @@ func (k *Key) Times(delim string) []time.Time {
|
||||
// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
|
||||
// it will not be included to result list.
|
||||
func (k *Key) ValidFloat64s(delim string) []float64 {
|
||||
vals, _ := k.getFloat64s(delim, false, false)
|
||||
vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
|
||||
// not be included to result list.
|
||||
func (k *Key) ValidInts(delim string) []int {
|
||||
vals, _ := k.getInts(delim, false, false)
|
||||
vals, _ := k.parseInts(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
|
||||
// then it will not be included to result list.
|
||||
func (k *Key) ValidInt64s(delim string) []int64 {
|
||||
vals, _ := k.getInt64s(delim, false, false)
|
||||
vals, _ := k.parseInt64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
|
||||
// then it will not be included to result list.
|
||||
func (k *Key) ValidUints(delim string) []uint {
|
||||
vals, _ := k.getUints(delim, false, false)
|
||||
vals, _ := k.parseUints(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
|
||||
// integer, then it will not be included to result list.
|
||||
func (k *Key) ValidUint64s(delim string) []uint64 {
|
||||
vals, _ := k.getUint64s(delim, false, false)
|
||||
vals, _ := k.parseUint64s(k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||
func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
|
||||
vals, _ := k.getTimesFormat(format, delim, false, false)
|
||||
vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
|
||||
return vals
|
||||
}
|
||||
|
||||
@@ -490,33 +562,33 @@ func (k *Key) ValidTimes(delim string) []time.Time {
|
||||
|
||||
// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
|
||||
return k.getFloat64s(delim, false, true)
|
||||
return k.parseFloat64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictInts returns list of int divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictInts(delim string) ([]int, error) {
|
||||
return k.getInts(delim, false, true)
|
||||
return k.parseInts(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictInt64s(delim string) ([]int64, error) {
|
||||
return k.getInt64s(delim, false, true)
|
||||
return k.parseInt64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictUints(delim string) ([]uint, error) {
|
||||
return k.getUints(delim, false, true)
|
||||
return k.parseUints(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
|
||||
func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
|
||||
return k.getUint64s(delim, false, true)
|
||||
return k.parseUint64s(k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
|
||||
// or error on first invalid input.
|
||||
func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
|
||||
return k.getTimesFormat(format, delim, false, true)
|
||||
return k.parseTimesFormat(format, k.Strings(delim), false, true)
|
||||
}
|
||||
|
||||
// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
|
||||
@@ -525,9 +597,8 @@ func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
|
||||
return k.StrictTimesFormat(time.RFC3339, delim)
|
||||
}
|
||||
|
||||
// getFloat64s returns list of float64 divided by given delimiter.
|
||||
func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]float64, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseFloat64s transforms strings to float64s.
|
||||
func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
|
||||
vals := make([]float64, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := strconv.ParseFloat(str, 64)
|
||||
@@ -541,9 +612,8 @@ func (k *Key) getFloat64s(delim string, addInvalid, returnOnInvalid bool) ([]flo
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// getInts returns list of int divided by given delimiter.
|
||||
func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseInts transforms strings to ints.
|
||||
func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
|
||||
vals := make([]int, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := strconv.Atoi(str)
|
||||
@@ -557,9 +627,8 @@ func (k *Key) getInts(delim string, addInvalid, returnOnInvalid bool) ([]int, er
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// getInt64s returns list of int64 divided by given delimiter.
|
||||
func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseInt64s transforms strings to int64s.
|
||||
func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
|
||||
vals := make([]int64, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := strconv.ParseInt(str, 10, 64)
|
||||
@@ -573,9 +642,8 @@ func (k *Key) getInt64s(delim string, addInvalid, returnOnInvalid bool) ([]int64
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// getUints returns list of uint divided by given delimiter.
|
||||
func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseUints transforms strings to uints.
|
||||
func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
|
||||
vals := make([]uint, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := strconv.ParseUint(str, 10, 0)
|
||||
@@ -589,9 +657,8 @@ func (k *Key) getUints(delim string, addInvalid, returnOnInvalid bool) ([]uint,
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// getUint64s returns list of uint64 divided by given delimiter.
|
||||
func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseUint64s transforms strings to uint64s.
|
||||
func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
|
||||
vals := make([]uint64, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := strconv.ParseUint(str, 10, 64)
|
||||
@@ -605,9 +672,8 @@ func (k *Key) getUint64s(delim string, addInvalid, returnOnInvalid bool) ([]uint
|
||||
return vals, nil
|
||||
}
|
||||
|
||||
// getTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
|
||||
func (k *Key) getTimesFormat(format, delim string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
|
||||
strs := k.Strings(delim)
|
||||
// parseTimesFormat transforms strings to times in given format.
|
||||
func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
|
||||
vals := make([]time.Time, 0, len(strs))
|
||||
for _, str := range strs {
|
||||
val, err := time.Parse(format, str)
|
||||
|
||||
+573
@@ -0,0 +1,573 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Key(t *testing.T) {
|
||||
Convey("Test getting and setting values", t, func() {
|
||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
Convey("Get values in default section", func() {
|
||||
sec := cfg.Section("")
|
||||
So(sec, ShouldNotBeNil)
|
||||
So(sec.Key("NAME").Value(), ShouldEqual, "ini")
|
||||
So(sec.Key("NAME").String(), ShouldEqual, "ini")
|
||||
So(sec.Key("NAME").Validate(func(in string) string {
|
||||
return in
|
||||
}), ShouldEqual, "ini")
|
||||
So(sec.Key("NAME").Comment, ShouldEqual, "; Package name")
|
||||
So(sec.Key("IMPORT_PATH").String(), ShouldEqual, "gopkg.in/ini.v1")
|
||||
})
|
||||
|
||||
Convey("Get values in non-default section", func() {
|
||||
sec := cfg.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")
|
||||
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()
|
||||
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()
|
||||
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")
|
||||
})
|
||||
|
||||
Convey("Get sections", func() {
|
||||
sections := cfg.Sections()
|
||||
for i, name := range []string{DEFAULT_SECTION, "author", "package", "package.sub", "features", "types", "array", "note", "comments", "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")
|
||||
})
|
||||
|
||||
Convey("Get multiple line value", func() {
|
||||
So(cfg.Section("author").Key("BIO").String(), ShouldEqual, "Gopher.\nCoding addict.\nGood man.\n")
|
||||
})
|
||||
|
||||
Convey("Get values with type", func() {
|
||||
sec := cfg.Section("types")
|
||||
v1, err := sec.Key("BOOL").Bool()
|
||||
So(err, ShouldBeNil)
|
||||
So(v1, ShouldBeTrue)
|
||||
|
||||
v1, err = sec.Key("BOOL_FALSE").Bool()
|
||||
So(err, ShouldBeNil)
|
||||
So(v1, ShouldBeFalse)
|
||||
|
||||
v2, err := sec.Key("FLOAT64").Float64()
|
||||
So(err, ShouldBeNil)
|
||||
So(v2, ShouldEqual, 1.25)
|
||||
|
||||
v3, err := sec.Key("INT").Int()
|
||||
So(err, ShouldBeNil)
|
||||
So(v3, ShouldEqual, 10)
|
||||
|
||||
v4, err := sec.Key("INT").Int64()
|
||||
So(err, ShouldBeNil)
|
||||
So(v4, ShouldEqual, 10)
|
||||
|
||||
v5, err := sec.Key("UINT").Uint()
|
||||
So(err, ShouldBeNil)
|
||||
So(v5, ShouldEqual, 3)
|
||||
|
||||
v6, err := sec.Key("UINT").Uint64()
|
||||
So(err, ShouldBeNil)
|
||||
So(v6, ShouldEqual, 3)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
v7, err := sec.Key("TIME").Time()
|
||||
So(err, ShouldBeNil)
|
||||
So(v7.String(), ShouldEqual, t.String())
|
||||
|
||||
Convey("Must get values with type", func() {
|
||||
So(sec.Key("STRING").MustString("404"), ShouldEqual, "str")
|
||||
So(sec.Key("BOOL").MustBool(), ShouldBeTrue)
|
||||
So(sec.Key("FLOAT64").MustFloat64(), ShouldEqual, 1.25)
|
||||
So(sec.Key("INT").MustInt(), ShouldEqual, 10)
|
||||
So(sec.Key("INT").MustInt64(), ShouldEqual, 10)
|
||||
So(sec.Key("UINT").MustUint(), ShouldEqual, 3)
|
||||
So(sec.Key("UINT").MustUint64(), ShouldEqual, 3)
|
||||
So(sec.Key("TIME").MustTime().String(), ShouldEqual, t.String())
|
||||
|
||||
dur, err := time.ParseDuration("2h45m")
|
||||
So(err, ShouldBeNil)
|
||||
So(sec.Key("DURATION").MustDuration().Seconds(), ShouldEqual, dur.Seconds())
|
||||
|
||||
Convey("Must get values with default value", func() {
|
||||
So(sec.Key("STRING_404").MustString("404"), ShouldEqual, "404")
|
||||
So(sec.Key("BOOL_404").MustBool(true), ShouldBeTrue)
|
||||
So(sec.Key("FLOAT64_404").MustFloat64(2.5), ShouldEqual, 2.5)
|
||||
So(sec.Key("INT_404").MustInt(15), ShouldEqual, 15)
|
||||
So(sec.Key("INT64_404").MustInt64(15), ShouldEqual, 15)
|
||||
So(sec.Key("UINT_404").MustUint(6), ShouldEqual, 6)
|
||||
So(sec.Key("UINT64_404").MustUint64(6), ShouldEqual, 6)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "2014-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
So(sec.Key("TIME_404").MustTime(t).String(), ShouldEqual, t.String())
|
||||
|
||||
So(sec.Key("DURATION_404").MustDuration(dur).Seconds(), ShouldEqual, dur.Seconds())
|
||||
|
||||
Convey("Must should set default as key value", func() {
|
||||
So(sec.Key("STRING_404").String(), ShouldEqual, "404")
|
||||
So(sec.Key("BOOL_404").String(), ShouldEqual, "true")
|
||||
So(sec.Key("FLOAT64_404").String(), ShouldEqual, "2.5")
|
||||
So(sec.Key("INT_404").String(), ShouldEqual, "15")
|
||||
So(sec.Key("INT64_404").String(), ShouldEqual, "15")
|
||||
So(sec.Key("UINT_404").String(), ShouldEqual, "6")
|
||||
So(sec.Key("UINT64_404").String(), ShouldEqual, "6")
|
||||
So(sec.Key("TIME_404").String(), ShouldEqual, "2014-01-01T20:17:05Z")
|
||||
So(sec.Key("DURATION_404").String(), ShouldEqual, "2h45m0s")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Get value with candidates", func() {
|
||||
sec := cfg.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)
|
||||
So(sec.Key("INT").InInt64(0, []int64{10, 20, 30}), ShouldEqual, 10)
|
||||
So(sec.Key("UINT").InUint(0, []uint{3, 6, 9}), ShouldEqual, 3)
|
||||
So(sec.Key("UINT").InUint64(0, []uint64{3, 6, 9}), ShouldEqual, 3)
|
||||
|
||||
zt, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
||||
So(err, ShouldBeNil)
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
So(sec.Key("TIME").InTime(zt, []time.Time{t, time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
||||
|
||||
Convey("Get value with candidates and default value", func() {
|
||||
So(sec.Key("STRING_404").In("str", []string{"str", "arr", "types"}), ShouldEqual, "str")
|
||||
So(sec.Key("FLOAT64_404").InFloat64(1.25, []float64{1.25, 2.5, 3.75}), ShouldEqual, 1.25)
|
||||
So(sec.Key("INT_404").InInt(10, []int{10, 20, 30}), ShouldEqual, 10)
|
||||
So(sec.Key("INT64_404").InInt64(10, []int64{10, 20, 30}), ShouldEqual, 10)
|
||||
So(sec.Key("UINT_404").InUint(3, []uint{3, 6, 9}), ShouldEqual, 3)
|
||||
So(sec.Key("UINT_404").InUint64(3, []uint64{3, 6, 9}), ShouldEqual, 3)
|
||||
So(sec.Key("TIME_404").InTime(t, []time.Time{time.Now(), time.Now(), time.Now().Add(1 * time.Second)}).String(), ShouldEqual, t.String())
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Get values in range", func() {
|
||||
sec := cfg.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)
|
||||
|
||||
minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
|
||||
So(err, ShouldBeNil)
|
||||
midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
|
||||
So(err, ShouldBeNil)
|
||||
maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
|
||||
So(err, ShouldBeNil)
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
|
||||
|
||||
Convey("Get value in range with default value", func() {
|
||||
So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
|
||||
So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
|
||||
So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
|
||||
So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Get values into slice", func() {
|
||||
sec := cfg.Section("array")
|
||||
So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
|
||||
So(len(sec.Key("STRINGS_404").Strings(",")), ShouldEqual, 0)
|
||||
|
||||
vals1 := sec.Key("FLOAT64S").Float64s(",")
|
||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||
|
||||
vals2 := sec.Key("INTS").Ints(",")
|
||||
intsEqual(vals2, 1, 2, 3)
|
||||
|
||||
vals3 := sec.Key("INTS").Int64s(",")
|
||||
int64sEqual(vals3, 1, 2, 3)
|
||||
|
||||
vals4 := sec.Key("UINTS").Uints(",")
|
||||
uintsEqual(vals4, 1, 2, 3)
|
||||
|
||||
vals5 := sec.Key("UINTS").Uint64s(",")
|
||||
uint64sEqual(vals5, 1, 2, 3)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
vals6 := sec.Key("TIMES").Times(",")
|
||||
timesEqual(vals6, t, t, t)
|
||||
})
|
||||
|
||||
Convey("Get valid values into slice", func() {
|
||||
sec := cfg.Section("array")
|
||||
vals1 := sec.Key("FLOAT64S").ValidFloat64s(",")
|
||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||
|
||||
vals2 := sec.Key("INTS").ValidInts(",")
|
||||
intsEqual(vals2, 1, 2, 3)
|
||||
|
||||
vals3 := sec.Key("INTS").ValidInt64s(",")
|
||||
int64sEqual(vals3, 1, 2, 3)
|
||||
|
||||
vals4 := sec.Key("UINTS").ValidUints(",")
|
||||
uintsEqual(vals4, 1, 2, 3)
|
||||
|
||||
vals5 := sec.Key("UINTS").ValidUint64s(",")
|
||||
uint64sEqual(vals5, 1, 2, 3)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
vals6 := sec.Key("TIMES").ValidTimes(",")
|
||||
timesEqual(vals6, t, t, t)
|
||||
})
|
||||
|
||||
Convey("Get values one type into slice of another type", func() {
|
||||
sec := cfg.Section("array")
|
||||
vals1 := sec.Key("STRINGS").ValidFloat64s(",")
|
||||
So(vals1, ShouldBeEmpty)
|
||||
|
||||
vals2 := sec.Key("STRINGS").ValidInts(",")
|
||||
So(vals2, ShouldBeEmpty)
|
||||
|
||||
vals3 := sec.Key("STRINGS").ValidInt64s(",")
|
||||
So(vals3, ShouldBeEmpty)
|
||||
|
||||
vals4 := sec.Key("STRINGS").ValidUints(",")
|
||||
So(vals4, ShouldBeEmpty)
|
||||
|
||||
vals5 := sec.Key("STRINGS").ValidUint64s(",")
|
||||
So(vals5, ShouldBeEmpty)
|
||||
|
||||
vals6 := sec.Key("STRINGS").ValidTimes(",")
|
||||
So(vals6, ShouldBeEmpty)
|
||||
})
|
||||
|
||||
Convey("Get valid values into slice without errors", func() {
|
||||
sec := cfg.Section("array")
|
||||
vals1, err := sec.Key("FLOAT64S").StrictFloat64s(",")
|
||||
So(err, ShouldBeNil)
|
||||
float64sEqual(vals1, 1.1, 2.2, 3.3)
|
||||
|
||||
vals2, err := sec.Key("INTS").StrictInts(",")
|
||||
So(err, ShouldBeNil)
|
||||
intsEqual(vals2, 1, 2, 3)
|
||||
|
||||
vals3, err := sec.Key("INTS").StrictInt64s(",")
|
||||
So(err, ShouldBeNil)
|
||||
int64sEqual(vals3, 1, 2, 3)
|
||||
|
||||
vals4, err := sec.Key("UINTS").StrictUints(",")
|
||||
So(err, ShouldBeNil)
|
||||
uintsEqual(vals4, 1, 2, 3)
|
||||
|
||||
vals5, err := sec.Key("UINTS").StrictUint64s(",")
|
||||
So(err, ShouldBeNil)
|
||||
uint64sEqual(vals5, 1, 2, 3)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
vals6, err := sec.Key("TIMES").StrictTimes(",")
|
||||
So(err, ShouldBeNil)
|
||||
timesEqual(vals6, t, t, t)
|
||||
})
|
||||
|
||||
Convey("Get invalid values into slice", func() {
|
||||
sec := cfg.Section("array")
|
||||
vals1, err := sec.Key("STRINGS").StrictFloat64s(",")
|
||||
So(vals1, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
vals2, err := sec.Key("STRINGS").StrictInts(",")
|
||||
So(vals2, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
vals3, err := sec.Key("STRINGS").StrictInt64s(",")
|
||||
So(vals3, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
vals4, err := sec.Key("STRINGS").StrictUints(",")
|
||||
So(vals4, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
vals5, err := sec.Key("STRINGS").StrictUint64s(",")
|
||||
So(vals5, ShouldBeEmpty)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
vals6, err := sec.Key("STRINGS").StrictTimes(",")
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, 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)))
|
||||
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(err, ShouldBeNil)
|
||||
So(cfg.Section("").Key("key1").Value(), ShouldBeEmpty)
|
||||
})
|
||||
}
|
||||
|
||||
const _CONF_GIT_CONFIG = `
|
||||
[remote "origin"]
|
||||
url = https://github.com/Antergone/test1.git
|
||||
url = https://github.com/Antergone/test2.git
|
||||
`
|
||||
|
||||
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")
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
+17
-15
@@ -193,7 +193,7 @@ func hasSurroundedQuote(in string, quote byte) bool {
|
||||
strings.IndexByte(in[1:], quote) == len(in)-2
|
||||
}
|
||||
|
||||
func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
|
||||
func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
|
||||
line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
|
||||
if len(line) == 0 {
|
||||
return "", nil
|
||||
@@ -217,18 +217,21 @@ func (p *parser) readValue(in []byte, ignoreContinuation bool) (string, error) {
|
||||
return line[startIdx : pos+startIdx], nil
|
||||
}
|
||||
|
||||
// Won't be able to reach here if value only contains whitespace.
|
||||
// Won't be able to reach here if value only contains whitespace
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// Check continuation lines when desired.
|
||||
// Check continuation lines when desired
|
||||
if !ignoreContinuation && line[len(line)-1] == '\\' {
|
||||
return p.readContinuationLines(line[:len(line)-1])
|
||||
}
|
||||
|
||||
i := strings.IndexAny(line, "#;")
|
||||
if i > -1 {
|
||||
p.comment.WriteString(line[i:])
|
||||
line = strings.TrimSpace(line[:i])
|
||||
// Check if ignore inline comment
|
||||
if !ignoreInlineComment {
|
||||
i := strings.IndexAny(line, "#;")
|
||||
if i > -1 {
|
||||
p.comment.WriteString(line[i:])
|
||||
line = strings.TrimSpace(line[:i])
|
||||
}
|
||||
}
|
||||
|
||||
// Trim single quotes
|
||||
@@ -318,7 +321,7 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||
if err != nil {
|
||||
// Treat as boolean key when desired, and whole line is key name.
|
||||
if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
|
||||
kname, err := p.readValue(line, f.options.IgnoreContinuation)
|
||||
kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,17 +344,16 @@ func (f *File) parse(reader io.Reader) (err error) {
|
||||
p.count++
|
||||
}
|
||||
|
||||
key, err := section.NewKey(kname, "")
|
||||
value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := section.NewKey(kname, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.isAutoIncrement = isAutoIncr
|
||||
|
||||
value, err := p.readValue(line[offset:], f.options.IgnoreContinuation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.SetValue(value)
|
||||
key.Comment = strings.TrimSpace(p.comment.String())
|
||||
p.comment.Reset()
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2016 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_BOM(t *testing.T) {
|
||||
Convey("Test handling BOM", t, func() {
|
||||
Convey("UTF-8-BOM", func() {
|
||||
cfg, err := Load("testdata/UTF-8-BOM.ini")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
So(cfg.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")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("UTF-16-BE-BOM", func() {
|
||||
})
|
||||
})
|
||||
}
|
||||
+22
-6
@@ -68,16 +68,18 @@ func (s *Section) NewKey(name, val string) (*Key, error) {
|
||||
}
|
||||
|
||||
if inSlice(name, s.keyList) {
|
||||
s.keys[name].value = val
|
||||
if s.f.options.AllowShadows {
|
||||
if err := s.keys[name].addShadow(val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
s.keys[name].value = val
|
||||
}
|
||||
return s.keys[name], nil
|
||||
}
|
||||
|
||||
s.keyList = append(s.keyList, name)
|
||||
s.keys[name] = &Key{
|
||||
s: s,
|
||||
name: name,
|
||||
value: val,
|
||||
}
|
||||
s.keys[name] = newKey(s, name, val)
|
||||
s.keysHash[name] = val
|
||||
return s.keys[name], nil
|
||||
}
|
||||
@@ -230,3 +232,17 @@ func (s *Section) DeleteKey(name string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ChildSections returns a list of child sections of current section.
|
||||
// For example, "[parent.child1]" and "[parent.child12]" are child sections
|
||||
// of section "[parent]".
|
||||
func (s *Section) ChildSections() []*Section {
|
||||
prefix := s.name + "."
|
||||
children := make([]*Section, 0, 3)
|
||||
for _, name := range s.f.sectionList {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
children = append(children, s.f.sections[name])
|
||||
}
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package ini
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Section(t *testing.T) {
|
||||
Convey("Test CRD sections", t, func() {
|
||||
cfg, err := Load([]byte(_CONF_DATA), "testdata/conf.ini")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
Convey("Get section strings", func() {
|
||||
So(strings.Join(cfg.SectionStrings(), ","), ShouldEqual, "DEFAULT,author,package,package.sub,features,types,array,note,comments,advance")
|
||||
})
|
||||
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
So(val.String(), ShouldEqual, "C")
|
||||
})
|
||||
|
||||
Convey("Validate raw section", func() {
|
||||
So(cfg.Section("core_lesson").Body(), ShouldEqual, `my lesson state data – 1111111111111111111000000000000000001110000
|
||||
111111111111111111100000000000111000000000 – end my lesson state data`)
|
||||
})
|
||||
})
|
||||
}
|
||||
+32
-13
@@ -78,8 +78,14 @@ func parseDelim(actual string) string {
|
||||
var reflectTime = reflect.TypeOf(time.Now()).Kind()
|
||||
|
||||
// setSliceWithProperType sets proper values to slice based on its type.
|
||||
func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
|
||||
strs := key.Strings(delim)
|
||||
func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
|
||||
var strs []string
|
||||
if allowShadow {
|
||||
strs = key.StringsWithShadows(delim)
|
||||
} else {
|
||||
strs = key.Strings(delim)
|
||||
}
|
||||
|
||||
numVals := len(strs)
|
||||
if numVals == 0 {
|
||||
return nil
|
||||
@@ -92,17 +98,17 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
|
||||
case reflect.String:
|
||||
vals = strs
|
||||
case reflect.Int:
|
||||
vals = key.Ints(delim)
|
||||
vals, _ = key.parseInts(strs, true, false)
|
||||
case reflect.Int64:
|
||||
vals = key.Int64s(delim)
|
||||
vals, _ = key.parseInt64s(strs, true, false)
|
||||
case reflect.Uint:
|
||||
vals = key.Uints(delim)
|
||||
vals, _ = key.parseUints(strs, true, false)
|
||||
case reflect.Uint64:
|
||||
vals = key.Uint64s(delim)
|
||||
vals, _ = key.parseUint64s(strs, true, false)
|
||||
case reflect.Float64:
|
||||
vals = key.Float64s(delim)
|
||||
vals, _ = key.parseFloat64s(strs, true, false)
|
||||
case reflectTime:
|
||||
vals = key.Times(delim)
|
||||
vals, _ = key.parseTimesFormat(time.RFC3339, strs, true, false)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type '[]%s'", sliceOf)
|
||||
}
|
||||
@@ -133,7 +139,7 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string) error {
|
||||
// setWithProperType sets proper value to field based on its type,
|
||||
// but it does not return error for failing parsing,
|
||||
// because we want to use default value that is already assigned to strcut.
|
||||
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
|
||||
func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
if len(key.String()) == 0 {
|
||||
@@ -187,13 +193,25 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
|
||||
}
|
||||
field.Set(reflect.ValueOf(timeVal))
|
||||
case reflect.Slice:
|
||||
return setSliceWithProperType(key, field, delim)
|
||||
return setSliceWithProperType(key, field, delim, allowShadow)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type '%s'", t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
|
||||
opts := strings.SplitN(tag, ",", 3)
|
||||
rawName = opts[0]
|
||||
if len(opts) > 1 {
|
||||
omitEmpty = opts[1] == "omitempty"
|
||||
}
|
||||
if len(opts) > 2 {
|
||||
allowShadow = opts[2] == "allowshadow"
|
||||
}
|
||||
return rawName, omitEmpty, allowShadow
|
||||
}
|
||||
|
||||
func (s *Section) mapTo(val reflect.Value) error {
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
@@ -209,8 +227,8 @@ func (s *Section) mapTo(val reflect.Value) error {
|
||||
continue
|
||||
}
|
||||
|
||||
opts := strings.SplitN(tag, ",", 2) // strip off possible omitempty
|
||||
fieldName := s.parseFieldName(tpField.Name, opts[0])
|
||||
rawName, _, allowShadow := parseTagOptions(tag)
|
||||
fieldName := s.parseFieldName(tpField.Name, rawName)
|
||||
if len(fieldName) == 0 || !field.CanSet() {
|
||||
continue
|
||||
}
|
||||
@@ -231,7 +249,8 @@ func (s *Section) mapTo(val reflect.Value) error {
|
||||
}
|
||||
|
||||
if key, err := s.GetKey(fieldName); err == nil {
|
||||
if err = setWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
|
||||
delim := parseDelim(tpField.Tag.Get("delim"))
|
||||
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
|
||||
return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
|
||||
}
|
||||
}
|
||||
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package ini
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type testNested struct {
|
||||
Cities []string `delim:"|"`
|
||||
Visits []time.Time
|
||||
Years []int
|
||||
Numbers []int64
|
||||
Ages []uint
|
||||
Populations []uint64
|
||||
Coordinates []float64
|
||||
Note string
|
||||
Unused int `ini:"-"`
|
||||
}
|
||||
|
||||
type testEmbeded struct {
|
||||
GPA float64
|
||||
}
|
||||
|
||||
type testStruct struct {
|
||||
Name string `ini:"NAME"`
|
||||
Age int
|
||||
Male bool
|
||||
Money float64
|
||||
Born time.Time
|
||||
Time time.Duration `ini:"Duration"`
|
||||
Others testNested
|
||||
*testEmbeded `ini:"grade"`
|
||||
Unused int `ini:"-"`
|
||||
Unsigned uint
|
||||
Omitted bool `ini:"omitthis,omitempty"`
|
||||
Shadows []string `ini:",,allowshadow"`
|
||||
ShadowInts []int `ini:"Shadows,,allowshadow"`
|
||||
}
|
||||
|
||||
const _CONF_DATA_STRUCT = `
|
||||
NAME = Unknwon
|
||||
Age = 21
|
||||
Male = true
|
||||
Money = 1.25
|
||||
Born = 1993-10-07T20:17:05Z
|
||||
Duration = 2h45m
|
||||
Unsigned = 3
|
||||
omitthis = true
|
||||
Shadows = 1, 2
|
||||
Shadows = 3, 4
|
||||
|
||||
[Others]
|
||||
Cities = HangZhou|Boston
|
||||
Visits = 1993-10-07T20:17:05Z, 1993-10-07T20:17:05Z
|
||||
Years = 1993,1994
|
||||
Numbers = 10010,10086
|
||||
Ages = 18,19
|
||||
Populations = 12345678,98765432
|
||||
Coordinates = 192.168,10.11
|
||||
Note = Hello world!
|
||||
|
||||
[grade]
|
||||
GPA = 2.8
|
||||
|
||||
[foo.bar]
|
||||
Here = there
|
||||
When = then
|
||||
`
|
||||
|
||||
type unsupport struct {
|
||||
Byte byte
|
||||
}
|
||||
|
||||
type unsupport2 struct {
|
||||
Others struct {
|
||||
Cities byte
|
||||
}
|
||||
}
|
||||
|
||||
type unsupport3 struct {
|
||||
Cities byte
|
||||
}
|
||||
|
||||
type unsupport4 struct {
|
||||
*unsupport3 `ini:"Others"`
|
||||
}
|
||||
|
||||
type defaultValue struct {
|
||||
Name string
|
||||
Age int
|
||||
Male bool
|
||||
Money float64
|
||||
Born time.Time
|
||||
Cities []string
|
||||
}
|
||||
|
||||
type fooBar struct {
|
||||
Here, When string
|
||||
}
|
||||
|
||||
const _INVALID_DATA_CONF_STRUCT = `
|
||||
Name =
|
||||
Age = age
|
||||
Male = 123
|
||||
Money = money
|
||||
Born = nil
|
||||
Cities =
|
||||
`
|
||||
|
||||
func Test_Struct(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(ts.Name, ShouldEqual, "Unknwon")
|
||||
So(ts.Age, ShouldEqual, 21)
|
||||
So(ts.Male, ShouldBeTrue)
|
||||
So(ts.Money, ShouldEqual, 1.25)
|
||||
So(ts.Unsigned, ShouldEqual, 3)
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
So(ts.Born.String(), ShouldEqual, t.String())
|
||||
|
||||
dur, err := time.ParseDuration("2h45m")
|
||||
So(err, ShouldBeNil)
|
||||
So(ts.Time.Seconds(), ShouldEqual, dur.Seconds())
|
||||
|
||||
So(strings.Join(ts.Others.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
||||
So(ts.Others.Visits[0].String(), ShouldEqual, t.String())
|
||||
So(fmt.Sprint(ts.Others.Years), ShouldEqual, "[1993 1994]")
|
||||
So(fmt.Sprint(ts.Others.Numbers), ShouldEqual, "[10010 10086]")
|
||||
So(fmt.Sprint(ts.Others.Ages), ShouldEqual, "[18 19]")
|
||||
So(fmt.Sprint(ts.Others.Populations), ShouldEqual, "[12345678 98765432]")
|
||||
So(fmt.Sprint(ts.Others.Coordinates), ShouldEqual, "[192.168 10.11]")
|
||||
So(ts.Others.Note, ShouldEqual, "Hello world!")
|
||||
So(ts.testEmbeded.GPA, ShouldEqual, 2.8)
|
||||
})
|
||||
|
||||
Convey("Map section to struct", func() {
|
||||
foobar := new(fooBar)
|
||||
f, err := Load([]byte(_CONF_DATA_STRUCT))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(f.Section("foo.bar").MapTo(foobar), ShouldBeNil)
|
||||
So(foobar.Here, ShouldEqual, "there")
|
||||
So(foobar.When, ShouldEqual, "then")
|
||||
})
|
||||
|
||||
Convey("Map to non-pointer struct", func() {
|
||||
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
So(cfg.MapTo(testStruct{}), ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Map to unsupported type", func() {
|
||||
cfg, err := Load([]byte(_CONF_DATA_STRUCT))
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
cfg.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)
|
||||
})
|
||||
|
||||
Convey("Map to omitempty field", func() {
|
||||
ts := new(testStruct)
|
||||
So(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))
|
||||
So(err, ShouldBeNil)
|
||||
ts := new(testStruct)
|
||||
So(cfg.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)
|
||||
})
|
||||
|
||||
Convey("Map to wrong types and gain default values", func() {
|
||||
cfg, err := 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(dv.Name, ShouldEqual, "Joe")
|
||||
So(dv.Age, ShouldEqual, 10)
|
||||
So(dv.Male, ShouldBeTrue)
|
||||
So(dv.Money, ShouldEqual, 1.25)
|
||||
So(dv.Born.String(), ShouldEqual, t.String())
|
||||
So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Reflect from struct", t, func() {
|
||||
type Embeded struct {
|
||||
Dates []time.Time `delim:"|"`
|
||||
Places []string
|
||||
Years []int
|
||||
Numbers []int64
|
||||
Ages []uint
|
||||
Populations []uint64
|
||||
Coordinates []float64
|
||||
None []int
|
||||
}
|
||||
type Author struct {
|
||||
Name string `ini:"NAME"`
|
||||
Male bool
|
||||
Age int
|
||||
Height uint
|
||||
GPA float64
|
||||
Date time.Time
|
||||
NeverMind string `ini:"-"`
|
||||
*Embeded `ini:"infos"`
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
|
||||
So(err, ShouldBeNil)
|
||||
a := &Author{"Unknwon", true, 21, 100, 2.8, t, "",
|
||||
&Embeded{
|
||||
[]time.Time{t, t},
|
||||
[]string{"HangZhou", "Boston"},
|
||||
[]int{1993, 1994},
|
||||
[]int64{10010, 10086},
|
||||
[]uint{18, 19},
|
||||
[]uint64{12345678, 98765432},
|
||||
[]float64{192.168, 10.11},
|
||||
[]int{},
|
||||
}}
|
||||
cfg := Empty()
|
||||
So(ReflectFrom(cfg, a), ShouldBeNil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = cfg.WriteTo(&buf)
|
||||
So(err, ShouldBeNil)
|
||||
So(buf.String(), ShouldEqual, `NAME = Unknwon
|
||||
Male = true
|
||||
Age = 21
|
||||
Height = 100
|
||||
GPA = 2.8
|
||||
Date = 1993-10-07T20:17:05Z
|
||||
|
||||
[infos]
|
||||
Dates = 1993-10-07T20:17:05Z|1993-10-07T20:17:05Z
|
||||
Places = HangZhou,Boston
|
||||
Years = 1993,1994
|
||||
Numbers = 10010,10086
|
||||
Ages = 18,19
|
||||
Populations = 12345678,98765432
|
||||
Coordinates = 192.168,10.11
|
||||
None =
|
||||
|
||||
`)
|
||||
|
||||
Convey("Reflect from non-point struct", func() {
|
||||
So(ReflectFrom(cfg, Author{}), ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Reflect from struct with omitempty", func() {
|
||||
cfg := Empty()
|
||||
type SpecialStruct struct {
|
||||
FirstName string `ini:"first_name"`
|
||||
LastName string `ini:"last_name"`
|
||||
JustOmitMe string `ini:"omitempty"`
|
||||
LastLogin time.Time `ini:"last_login,omitempty"`
|
||||
LastLogin2 time.Time `ini:",omitempty"`
|
||||
NotEmpty int `ini:"omitempty"`
|
||||
}
|
||||
|
||||
So(ReflectFrom(cfg, &SpecialStruct{FirstName: "John", LastName: "Doe", NotEmpty: 9}), ShouldBeNil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = cfg.WriteTo(&buf)
|
||||
So(buf.String(), ShouldEqual, `first_name = John
|
||||
last_name = Doe
|
||||
omitempty = 9
|
||||
|
||||
`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type testMapper struct {
|
||||
PackageName string
|
||||
}
|
||||
|
||||
func Test_NameGetter(t *testing.T) {
|
||||
Convey("Test name mappers", t, func() {
|
||||
So(MapToWithMapper(&testMapper{}, TitleUnderscore, []byte("packag_name=ini")), ShouldBeNil)
|
||||
|
||||
cfg, err := Load([]byte("PACKAGE_NAME=ini"))
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
|
||||
cfg.NameMapper = AllCapsUnderscore
|
||||
tg := new(testMapper)
|
||||
So(cfg.MapTo(tg), ShouldBeNil)
|
||||
So(tg.PackageName, ShouldEqual, "ini")
|
||||
})
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+2
@@ -0,0 +1,2 @@
|
||||
[author]
|
||||
E-MAIL = u@gogs.io
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
[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><L.Slide#2> This slide has the fuel listed in the wrong units <e.1>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
[author]
|
||||
E-MAIL = u@gogs.io
|
||||
Reference in New Issue
Block a user