Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65ca806488 | |||
| 5c94d86029 | |||
| b76eb62117 | |||
| 196ce3a1f6 | |||
| 9f8f82dfe8 | |||
| 661484ae7e | |||
| 34de94e6a8 | |||
| 88263a05cc | |||
| 1dbe20e76c | |||
| 05bf3807d3 | |||
| 06838de5d2 | |||
| db62263e3e | |||
| 2d866e3fae | |||
| 100799f7b7 | |||
| ecd155a62f | |||
| bcacc71a18 | |||
| 71c324cf7b | |||
| 4c840f1b8b | |||
| 5060c72d94 | |||
| 0a459e938d |
+20
-20
@@ -13,9 +13,9 @@ stages:
|
|||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.14"
|
displayName: "Install Go 1.15"
|
||||||
inputs:
|
inputs:
|
||||||
version: "1.14"
|
version: "1.15"
|
||||||
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
||||||
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
|
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
|
||||||
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
|
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
|
||||||
@@ -36,9 +36,9 @@ stages:
|
|||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.14"
|
displayName: "Install Go 1.15"
|
||||||
inputs:
|
inputs:
|
||||||
version: "1.14"
|
version: "1.15"
|
||||||
- task: Go@0
|
- task: Go@0
|
||||||
displayName: "go fmt ./..."
|
displayName: "go fmt ./..."
|
||||||
inputs:
|
inputs:
|
||||||
@@ -51,9 +51,9 @@ stages:
|
|||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.14"
|
displayName: "Install Go 1.15"
|
||||||
inputs:
|
inputs:
|
||||||
version: "1.14"
|
version: "1.15"
|
||||||
- task: Go@0
|
- task: Go@0
|
||||||
displayName: "Generate coverage"
|
displayName: "Generate coverage"
|
||||||
inputs:
|
inputs:
|
||||||
@@ -71,9 +71,9 @@ stages:
|
|||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.14"
|
displayName: "Install Go 1.15"
|
||||||
inputs:
|
inputs:
|
||||||
version: "1.14"
|
version: "1.15"
|
||||||
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
inputs:
|
inputs:
|
||||||
@@ -86,9 +86,9 @@ stages:
|
|||||||
vmImage: ubuntu-latest
|
vmImage: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go 1.14"
|
displayName: "Install Go 1.15"
|
||||||
inputs:
|
inputs:
|
||||||
version: "1.14"
|
version: "1.15"
|
||||||
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
||||||
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
|
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
|
||||||
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
|
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
|
||||||
@@ -102,6 +102,15 @@ stages:
|
|||||||
displayName: "unit tests"
|
displayName: "unit tests"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
linux 1.15:
|
||||||
|
goVersion: '1.15'
|
||||||
|
imageName: 'ubuntu-latest'
|
||||||
|
mac 1.15:
|
||||||
|
goVersion: '1.15'
|
||||||
|
imageName: 'macOS-latest'
|
||||||
|
windows 1.15:
|
||||||
|
goVersion: '1.15'
|
||||||
|
imageName: 'windows-latest'
|
||||||
linux 1.14:
|
linux 1.14:
|
||||||
goVersion: '1.14'
|
goVersion: '1.14'
|
||||||
imageName: 'ubuntu-latest'
|
imageName: 'ubuntu-latest'
|
||||||
@@ -111,15 +120,6 @@ stages:
|
|||||||
windows 1.14:
|
windows 1.14:
|
||||||
goVersion: '1.14'
|
goVersion: '1.14'
|
||||||
imageName: 'windows-latest'
|
imageName: 'windows-latest'
|
||||||
linux 1.13:
|
|
||||||
goVersion: '1.13'
|
|
||||||
imageName: 'ubuntu-latest'
|
|
||||||
mac 1.13:
|
|
||||||
goVersion: '1.13'
|
|
||||||
imageName: 'macOS-latest'
|
|
||||||
windows 1.13:
|
|
||||||
goVersion: '1.13'
|
|
||||||
imageName: 'windows-latest'
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: $(imageName)
|
vmImage: $(imageName)
|
||||||
steps:
|
steps:
|
||||||
@@ -155,7 +155,7 @@ stages:
|
|||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
displayName: "Install Go"
|
displayName: "Install Go"
|
||||||
inputs:
|
inputs:
|
||||||
version: 1.14
|
version: 1.15
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
inputs:
|
inputs:
|
||||||
targetType: inline
|
targetType: inline
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null
|
|||||||
pushd ${ref_tempdir} >/dev/null
|
pushd ${ref_tempdir} >/dev/null
|
||||||
git checkout ${reference_ref} >/dev/null 2>/dev/null
|
git checkout ${reference_ref} >/dev/null 2>/dev/null
|
||||||
go test -bench=. -benchmem | tee ${ref_benchmark}
|
go test -bench=. -benchmem | tee ${ref_benchmark}
|
||||||
|
cd benchmark
|
||||||
|
go test -bench=. -benchmem | tee -a ${ref_benchmark}
|
||||||
popd >/dev/null
|
popd >/dev/null
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== local"
|
echo "=== local"
|
||||||
go test -bench=. -benchmem | tee ${local_benchmark}
|
go test -bench=. -benchmem | tee ${local_benchmark}
|
||||||
|
cd benchmark
|
||||||
|
go test -bench=. -benchmem | tee -a ${local_benchmark}
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== diff"
|
echo "=== diff"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package toml
|
package benchmark
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -8,7 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
burntsushi "github.com/BurntSushi/toml"
|
burntsushi "github.com/BurntSushi/toml"
|
||||||
yaml "gopkg.in/yaml.v2"
|
"github.com/pelletier/go-toml"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type benchmarkDoc struct {
|
type benchmarkDoc struct {
|
||||||
@@ -124,7 +125,7 @@ func BenchmarkParseToml(b *testing.B) {
|
|||||||
}
|
}
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := LoadReader(bytes.NewReader(fileBytes))
|
_, err := toml.LoadReader(bytes.NewReader(fileBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,7 @@ func BenchmarkUnmarshalToml(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
target := benchmarkDoc{}
|
target := benchmarkDoc{}
|
||||||
err := Unmarshal(bytes, &target)
|
err := toml.Unmarshal(bytes, &target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
module github.com/pelletier/go-toml/benchmark
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.3.1
|
||||||
|
github.com/pelletier/go-toml v0.0.0
|
||||||
|
gopkg.in/yaml.v2 v2.3.0
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/pelletier/go-toml => ../
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
@@ -2,8 +2,4 @@ module github.com/pelletier/go-toml
|
|||||||
|
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require (
|
require github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/BurntSushi/toml v0.3.1
|
|
||||||
github.com/davecgh/go-spew v1.1.1
|
|
||||||
gopkg.in/yaml.v2 v2.3.0
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ func (l *tomlLexer) lexComma() tomlLexStateFn {
|
|||||||
// Parse the key and emits its value without escape sequences.
|
// Parse the key and emits its value without escape sequences.
|
||||||
// bare keys, basic string keys and literal string keys are supported.
|
// bare keys, basic string keys and literal string keys are supported.
|
||||||
func (l *tomlLexer) lexKey() tomlLexStateFn {
|
func (l *tomlLexer) lexKey() tomlLexStateFn {
|
||||||
growingString := ""
|
var sb strings.Builder
|
||||||
|
|
||||||
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
@@ -315,7 +315,9 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return l.errorf(err.Error())
|
return l.errorf(err.Error())
|
||||||
}
|
}
|
||||||
growingString += "\"" + str + "\""
|
sb.WriteString("\"")
|
||||||
|
sb.WriteString(str)
|
||||||
|
sb.WriteString("\"")
|
||||||
l.next()
|
l.next()
|
||||||
continue
|
continue
|
||||||
} else if r == '\'' {
|
} else if r == '\'' {
|
||||||
@@ -324,41 +326,45 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return l.errorf(err.Error())
|
return l.errorf(err.Error())
|
||||||
}
|
}
|
||||||
growingString += "'" + str + "'"
|
sb.WriteString("'")
|
||||||
|
sb.WriteString(str)
|
||||||
|
sb.WriteString("'")
|
||||||
l.next()
|
l.next()
|
||||||
continue
|
continue
|
||||||
} else if r == '\n' {
|
} else if r == '\n' {
|
||||||
return l.errorf("keys cannot contain new lines")
|
return l.errorf("keys cannot contain new lines")
|
||||||
} else if isSpace(r) {
|
} else if isSpace(r) {
|
||||||
str := " "
|
var str strings.Builder
|
||||||
|
str.WriteString(" ")
|
||||||
|
|
||||||
// skip trailing whitespace
|
// skip trailing whitespace
|
||||||
l.next()
|
l.next()
|
||||||
for r = l.peek(); isSpace(r); r = l.peek() {
|
for r = l.peek(); isSpace(r); r = l.peek() {
|
||||||
str += string(r)
|
str.WriteRune(r)
|
||||||
l.next()
|
l.next()
|
||||||
}
|
}
|
||||||
// break loop if not a dot
|
// break loop if not a dot
|
||||||
if r != '.' {
|
if r != '.' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
str += "."
|
str.WriteString(".")
|
||||||
// skip trailing whitespace after dot
|
// skip trailing whitespace after dot
|
||||||
l.next()
|
l.next()
|
||||||
for r = l.peek(); isSpace(r); r = l.peek() {
|
for r = l.peek(); isSpace(r); r = l.peek() {
|
||||||
str += string(r)
|
str.WriteRune(r)
|
||||||
l.next()
|
l.next()
|
||||||
}
|
}
|
||||||
growingString += str
|
sb.WriteString(str.String())
|
||||||
continue
|
continue
|
||||||
} else if r == '.' {
|
} else if r == '.' {
|
||||||
// skip
|
// skip
|
||||||
} else if !isValidBareChar(r) {
|
} else if !isValidBareChar(r) {
|
||||||
return l.errorf("keys cannot contain %c character", r)
|
return l.errorf("keys cannot contain %c character", r)
|
||||||
}
|
}
|
||||||
growingString += string(r)
|
sb.WriteRune(r)
|
||||||
l.next()
|
l.next()
|
||||||
}
|
}
|
||||||
l.emitWithValue(tokenKey, growingString)
|
l.emitWithValue(tokenKey, sb.String())
|
||||||
return l.lexVoid
|
return l.lexVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +389,7 @@ func (l *tomlLexer) lexLeftBracket() tomlLexStateFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) {
|
func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) {
|
||||||
growingString := ""
|
var sb strings.Builder
|
||||||
|
|
||||||
if discardLeadingNewLine {
|
if discardLeadingNewLine {
|
||||||
if l.follow("\r\n") {
|
if l.follow("\r\n") {
|
||||||
@@ -397,14 +403,14 @@ func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNe
|
|||||||
// find end of string
|
// find end of string
|
||||||
for {
|
for {
|
||||||
if l.follow(terminator) {
|
if l.follow(terminator) {
|
||||||
return growingString, nil
|
return sb.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
next := l.peek()
|
next := l.peek()
|
||||||
if next == eof {
|
if next == eof {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
growingString += string(l.next())
|
sb.WriteRune(l.next())
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("unclosed string")
|
return "", errors.New("unclosed string")
|
||||||
@@ -438,7 +444,7 @@ func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
|||||||
// Terminator is the substring indicating the end of the token.
|
// Terminator is the substring indicating the end of the token.
|
||||||
// The resulting string does not include the terminator.
|
// The resulting string does not include the terminator.
|
||||||
func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) {
|
func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) {
|
||||||
growingString := ""
|
var sb strings.Builder
|
||||||
|
|
||||||
if discardLeadingNewLine {
|
if discardLeadingNewLine {
|
||||||
if l.follow("\r\n") {
|
if l.follow("\r\n") {
|
||||||
@@ -451,7 +457,7 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||||||
|
|
||||||
for {
|
for {
|
||||||
if l.follow(terminator) {
|
if l.follow(terminator) {
|
||||||
return growingString, nil
|
return sb.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.follow("\\") {
|
if l.follow("\\") {
|
||||||
@@ -469,61 +475,61 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||||||
l.next()
|
l.next()
|
||||||
}
|
}
|
||||||
case '"':
|
case '"':
|
||||||
growingString += "\""
|
sb.WriteString("\"")
|
||||||
l.next()
|
l.next()
|
||||||
case 'n':
|
case 'n':
|
||||||
growingString += "\n"
|
sb.WriteString("\n")
|
||||||
l.next()
|
l.next()
|
||||||
case 'b':
|
case 'b':
|
||||||
growingString += "\b"
|
sb.WriteString("\b")
|
||||||
l.next()
|
l.next()
|
||||||
case 'f':
|
case 'f':
|
||||||
growingString += "\f"
|
sb.WriteString("\f")
|
||||||
l.next()
|
l.next()
|
||||||
case '/':
|
case '/':
|
||||||
growingString += "/"
|
sb.WriteString("/")
|
||||||
l.next()
|
l.next()
|
||||||
case 't':
|
case 't':
|
||||||
growingString += "\t"
|
sb.WriteString("\t")
|
||||||
l.next()
|
l.next()
|
||||||
case 'r':
|
case 'r':
|
||||||
growingString += "\r"
|
sb.WriteString("\r")
|
||||||
l.next()
|
l.next()
|
||||||
case '\\':
|
case '\\':
|
||||||
growingString += "\\"
|
sb.WriteString("\\")
|
||||||
l.next()
|
l.next()
|
||||||
case 'u':
|
case 'u':
|
||||||
l.next()
|
l.next()
|
||||||
code := ""
|
var code strings.Builder
|
||||||
for i := 0; i < 4; i++ {
|
for i := 0; i < 4; i++ {
|
||||||
c := l.peek()
|
c := l.peek()
|
||||||
if !isHexDigit(c) {
|
if !isHexDigit(c) {
|
||||||
return "", errors.New("unfinished unicode escape")
|
return "", errors.New("unfinished unicode escape")
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
code = code + string(c)
|
code.WriteRune(c)
|
||||||
}
|
}
|
||||||
intcode, err := strconv.ParseInt(code, 16, 32)
|
intcode, err := strconv.ParseInt(code.String(), 16, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("invalid unicode escape: \\u" + code)
|
return "", errors.New("invalid unicode escape: \\u" + code.String())
|
||||||
}
|
}
|
||||||
growingString += string(rune(intcode))
|
sb.WriteRune(rune(intcode))
|
||||||
case 'U':
|
case 'U':
|
||||||
l.next()
|
l.next()
|
||||||
code := ""
|
var code strings.Builder
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
c := l.peek()
|
c := l.peek()
|
||||||
if !isHexDigit(c) {
|
if !isHexDigit(c) {
|
||||||
return "", errors.New("unfinished unicode escape")
|
return "", errors.New("unfinished unicode escape")
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
code = code + string(c)
|
code.WriteRune(c)
|
||||||
}
|
}
|
||||||
intcode, err := strconv.ParseInt(code, 16, 64)
|
intcode, err := strconv.ParseInt(code.String(), 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("invalid unicode escape: \\U" + code)
|
return "", errors.New("invalid unicode escape: \\U" + code.String())
|
||||||
}
|
}
|
||||||
growingString += string(rune(intcode))
|
sb.WriteRune(rune(intcode))
|
||||||
default:
|
default:
|
||||||
return "", errors.New("invalid escape sequence: \\" + string(l.peek()))
|
return "", errors.New("invalid escape sequence: \\" + string(l.peek()))
|
||||||
}
|
}
|
||||||
@@ -534,7 +540,7 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||||||
return "", fmt.Errorf("unescaped control character %U", r)
|
return "", fmt.Errorf("unescaped control character %U", r)
|
||||||
}
|
}
|
||||||
l.next()
|
l.next()
|
||||||
growingString += string(r)
|
sb.WriteRune(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.peek() == eof {
|
if l.peek() == eof {
|
||||||
|
|||||||
+32
-3
@@ -76,6 +76,7 @@ var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
|
|||||||
var localDateType = reflect.TypeOf(LocalDate{})
|
var localDateType = reflect.TypeOf(LocalDate{})
|
||||||
var localTimeType = reflect.TypeOf(LocalTime{})
|
var localTimeType = reflect.TypeOf(LocalTime{})
|
||||||
var localDateTimeType = reflect.TypeOf(LocalDateTime{})
|
var localDateTimeType = reflect.TypeOf(LocalDateTime{})
|
||||||
|
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
|
||||||
|
|
||||||
// Check if the given marshal type maps to a Tree primitive
|
// Check if the given marshal type maps to a Tree primitive
|
||||||
func isPrimitive(mtype reflect.Type) bool {
|
func isPrimitive(mtype reflect.Type) bool {
|
||||||
@@ -436,6 +437,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
|
|||||||
if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon {
|
if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon {
|
||||||
e.appendTree(tval, tree)
|
e.appendTree(tval, tree)
|
||||||
} else {
|
} else {
|
||||||
|
val = e.wrapTomlValue(val, tval)
|
||||||
tval.SetPathWithOptions([]string{opts.name}, SetOptions{
|
tval.SetPathWithOptions([]string{opts.name}, SetOptions{
|
||||||
Comment: opts.comment,
|
Comment: opts.comment,
|
||||||
Commented: opts.commented,
|
Commented: opts.commented,
|
||||||
@@ -474,6 +476,7 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
val = e.wrapTomlValue(val, tval)
|
||||||
if e.quoteMapKeys {
|
if e.quoteMapKeys {
|
||||||
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine)
|
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -516,13 +519,13 @@ func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (int
|
|||||||
|
|
||||||
// Convert given marshal value to toml value
|
// Convert given marshal value to toml value
|
||||||
func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
|
func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
|
||||||
e.line++
|
|
||||||
if mtype.Kind() == reflect.Ptr {
|
if mtype.Kind() == reflect.Ptr {
|
||||||
switch {
|
switch {
|
||||||
case isCustomMarshaler(mtype):
|
case isCustomMarshaler(mtype):
|
||||||
return callCustomMarshaler(mval)
|
return callCustomMarshaler(mval)
|
||||||
case isTextMarshaler(mtype):
|
case isTextMarshaler(mtype):
|
||||||
return callTextMarshaler(mval)
|
b, err := callTextMarshaler(mval)
|
||||||
|
return string(b), err
|
||||||
default:
|
default:
|
||||||
return e.valueToToml(mtype.Elem(), mval.Elem())
|
return e.valueToToml(mtype.Elem(), mval.Elem())
|
||||||
}
|
}
|
||||||
@@ -534,7 +537,8 @@ func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface
|
|||||||
case isCustomMarshaler(mtype):
|
case isCustomMarshaler(mtype):
|
||||||
return callCustomMarshaler(mval)
|
return callCustomMarshaler(mval)
|
||||||
case isTextMarshaler(mtype):
|
case isTextMarshaler(mtype):
|
||||||
return callTextMarshaler(mval)
|
b, err := callTextMarshaler(mval)
|
||||||
|
return string(b), err
|
||||||
case isTree(mtype):
|
case isTree(mtype):
|
||||||
return e.valueToTree(mtype, mval)
|
return e.valueToTree(mtype, mval)
|
||||||
case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype):
|
case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype):
|
||||||
@@ -577,6 +581,25 @@ func (e *Encoder) appendTree(t, o *Tree) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a toml value with the current line number as the position line
|
||||||
|
func (e *Encoder) wrapTomlValue(val interface{}, parent *Tree) interface{} {
|
||||||
|
_, isTree := val.(*Tree)
|
||||||
|
_, isTreeS := val.([]*Tree)
|
||||||
|
if isTree || isTreeS {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &tomlValue{
|
||||||
|
value: val,
|
||||||
|
position: Position{
|
||||||
|
e.line,
|
||||||
|
parent.position.Col,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
e.line++
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v.
|
// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v.
|
||||||
// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for
|
// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for
|
||||||
// sub-structs, and only definite types can be unmarshaled.
|
// sub-structs, and only definite types can be unmarshaled.
|
||||||
@@ -681,6 +704,8 @@ func (d *Decoder) unmarshal(v interface{}) error {
|
|||||||
|
|
||||||
switch elem.Kind() {
|
switch elem.Kind() {
|
||||||
case reflect.Struct, reflect.Map:
|
case reflect.Struct, reflect.Map:
|
||||||
|
case reflect.Interface:
|
||||||
|
elem = mapStringInterfaceType
|
||||||
default:
|
default:
|
||||||
return errors.New("only a pointer to struct or map can be unmarshaled from TOML")
|
return errors.New("only a pointer to struct or map can be unmarshaled from TOML")
|
||||||
}
|
}
|
||||||
@@ -717,6 +742,10 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||||||
if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) {
|
if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) {
|
||||||
d.visitor.visitAll()
|
d.visitor.visitAll()
|
||||||
|
|
||||||
|
if tval == nil {
|
||||||
|
return mvalPtr.Elem(), nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil {
|
if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil {
|
||||||
return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err)
|
return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+148
-20
@@ -16,6 +16,7 @@ import (
|
|||||||
type basicMarshalTestStruct struct {
|
type basicMarshalTestStruct struct {
|
||||||
String string `toml:"Zstring"`
|
String string `toml:"Zstring"`
|
||||||
StringList []string `toml:"Ystrlist"`
|
StringList []string `toml:"Ystrlist"`
|
||||||
|
BasicMarshalTestSubAnonymousStruct
|
||||||
Sub basicMarshalTestSubStruct `toml:"Xsubdoc"`
|
Sub basicMarshalTestSubStruct `toml:"Xsubdoc"`
|
||||||
SubList []basicMarshalTestSubStruct `toml:"Wsublist"`
|
SubList []basicMarshalTestSubStruct `toml:"Wsublist"`
|
||||||
}
|
}
|
||||||
@@ -24,50 +25,58 @@ type basicMarshalTestSubStruct struct {
|
|||||||
String2 string
|
String2 string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BasicMarshalTestSubAnonymousStruct struct {
|
||||||
|
String3 string
|
||||||
|
}
|
||||||
|
|
||||||
var basicTestData = basicMarshalTestStruct{
|
var basicTestData = basicMarshalTestStruct{
|
||||||
String: "Hello",
|
String: "Hello",
|
||||||
StringList: []string{"Howdy", "Hey There"},
|
StringList: []string{"Howdy", "Hey There"},
|
||||||
Sub: basicMarshalTestSubStruct{"One"},
|
BasicMarshalTestSubAnonymousStruct: BasicMarshalTestSubAnonymousStruct{"One"},
|
||||||
SubList: []basicMarshalTestSubStruct{{"Two"}, {"Three"}},
|
Sub: basicMarshalTestSubStruct{"Two"},
|
||||||
|
SubList: []basicMarshalTestSubStruct{{"Three"}, {"Four"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
var basicTestToml = []byte(`Ystrlist = ["Howdy", "Hey There"]
|
var basicTestToml = []byte(`String3 = "One"
|
||||||
|
Ystrlist = ["Howdy", "Hey There"]
|
||||||
Zstring = "Hello"
|
Zstring = "Hello"
|
||||||
|
|
||||||
[[Wsublist]]
|
|
||||||
String2 = "Two"
|
|
||||||
|
|
||||||
[[Wsublist]]
|
[[Wsublist]]
|
||||||
String2 = "Three"
|
String2 = "Three"
|
||||||
|
|
||||||
|
[[Wsublist]]
|
||||||
|
String2 = "Four"
|
||||||
|
|
||||||
[Xsubdoc]
|
[Xsubdoc]
|
||||||
String2 = "One"
|
String2 = "Two"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var basicTestTomlCustomIndentation = []byte(`Ystrlist = ["Howdy", "Hey There"]
|
var basicTestTomlCustomIndentation = []byte(`String3 = "One"
|
||||||
|
Ystrlist = ["Howdy", "Hey There"]
|
||||||
Zstring = "Hello"
|
Zstring = "Hello"
|
||||||
|
|
||||||
[[Wsublist]]
|
|
||||||
String2 = "Two"
|
|
||||||
|
|
||||||
[[Wsublist]]
|
[[Wsublist]]
|
||||||
String2 = "Three"
|
String2 = "Three"
|
||||||
|
|
||||||
|
[[Wsublist]]
|
||||||
|
String2 = "Four"
|
||||||
|
|
||||||
[Xsubdoc]
|
[Xsubdoc]
|
||||||
String2 = "One"
|
String2 = "Two"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var basicTestTomlOrdered = []byte(`Zstring = "Hello"
|
var basicTestTomlOrdered = []byte(`Zstring = "Hello"
|
||||||
Ystrlist = ["Howdy", "Hey There"]
|
Ystrlist = ["Howdy", "Hey There"]
|
||||||
|
String3 = "One"
|
||||||
|
|
||||||
[Xsubdoc]
|
[Xsubdoc]
|
||||||
String2 = "One"
|
|
||||||
|
|
||||||
[[Wsublist]]
|
|
||||||
String2 = "Two"
|
String2 = "Two"
|
||||||
|
|
||||||
[[Wsublist]]
|
[[Wsublist]]
|
||||||
String2 = "Three"
|
String2 = "Three"
|
||||||
|
|
||||||
|
[[Wsublist]]
|
||||||
|
String2 = "Four"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
var marshalTestToml = []byte(`title = "TOML Marshal Testing"
|
var marshalTestToml = []byte(`title = "TOML Marshal Testing"
|
||||||
@@ -979,6 +988,40 @@ func TestCustomMarshaler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IntOrString string
|
||||||
|
|
||||||
|
func (x *IntOrString) MarshalTOML() ([]byte, error) {
|
||||||
|
s := *(*string)(x)
|
||||||
|
_, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return []byte(fmt.Sprintf(`"%s"`, s)), nil
|
||||||
|
}
|
||||||
|
return []byte(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedCustomMarshaler(t *testing.T) {
|
||||||
|
num := IntOrString("100")
|
||||||
|
str := IntOrString("hello")
|
||||||
|
var parent = struct {
|
||||||
|
IntField *IntOrString `toml:"int"`
|
||||||
|
StringField *IntOrString `toml:"string"`
|
||||||
|
}{
|
||||||
|
&num,
|
||||||
|
&str,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := Marshal(parent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := `int = 100
|
||||||
|
string = "hello"
|
||||||
|
`
|
||||||
|
if !bytes.Equal(result, []byte(expected)) {
|
||||||
|
t.Errorf("Bad nested text marshaler: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type textMarshaler struct {
|
type textMarshaler struct {
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
@@ -1079,7 +1122,7 @@ type customPointerMarshaler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) {
|
func (m *customPointerMarshaler) MarshalTOML() ([]byte, error) {
|
||||||
return []byte("hidden"), nil
|
return []byte(`"hidden"`), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type textPointerMarshaler struct {
|
type textPointerMarshaler struct {
|
||||||
@@ -2151,10 +2194,7 @@ func TestUnmarshalBadDuration(t *testing.T) {
|
|||||||
result := testBadDuration{}
|
result := testBadDuration{}
|
||||||
err := NewDecoder(buf).Decode(&result)
|
err := NewDecoder(buf).Decode(&result)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal()
|
t.Fatal("expected bad duration error")
|
||||||
}
|
|
||||||
if err.Error() != "(1, 1): Can't convert 1z(string) to time.Duration. time: unknown unit z in duration 1z" {
|
|
||||||
t.Fatalf("unexpected error: %s", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3848,3 +3888,91 @@ func TestPreserveNotEmptyField(t *testing.T) {
|
|||||||
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// github issue 432
|
||||||
|
func TestUnmarshalEmptyInterface(t *testing.T) {
|
||||||
|
doc := []byte(`User = "pelletier"`)
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
|
|
||||||
|
err := Unmarshal(doc, &v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if x["User"] != "pelletier" {
|
||||||
|
t.Fatalf("expected User=pelletier, but got %v", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalEmptyInterfaceDeep(t *testing.T) {
|
||||||
|
doc := []byte(`
|
||||||
|
User = "pelletier"
|
||||||
|
Age = 99
|
||||||
|
|
||||||
|
[foo]
|
||||||
|
bar = 42
|
||||||
|
`)
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
|
|
||||||
|
err := Unmarshal(doc, &v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
x, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"User": "pelletier",
|
||||||
|
"Age": 99,
|
||||||
|
"foo": map[string]interface{}{
|
||||||
|
"bar": 42,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reflect.DeepEqual(x, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Key string `toml:"key"`
|
||||||
|
Obj Custom `toml:"obj"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Custom struct {
|
||||||
|
v string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Custom) UnmarshalTOML(v interface{}) error {
|
||||||
|
c.v = "called"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGithubIssue431(t *testing.T) {
|
||||||
|
doc := `key = "value"`
|
||||||
|
tree, err := LoadBytes([]byte(doc))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
if err := tree.Unmarshal(&c); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Key != "value" {
|
||||||
|
t.Errorf("expected c.Key='value', not '%s'", c.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Obj.v == "called" {
|
||||||
|
t.Errorf("UnmarshalTOML should not have been called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,6 +122,89 @@ func (t *Tree) GetPath(keys []string) interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetArray returns the value at key in the Tree.
|
||||||
|
// It returns []string, []int64, etc type if key has homogeneous lists
|
||||||
|
// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings.
|
||||||
|
// Returns nil if the path does not exist in the tree.
|
||||||
|
// If keys is of length zero, the current tree is returned.
|
||||||
|
func (t *Tree) GetArray(key string) interface{} {
|
||||||
|
if key == "" {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return t.GetArrayPath(strings.Split(key, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArrayPath returns the element in the tree indicated by 'keys'.
|
||||||
|
// If keys is of length zero, the current tree is returned.
|
||||||
|
func (t *Tree) GetArrayPath(keys []string) interface{} {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
subtree := t
|
||||||
|
for _, intermediateKey := range keys[:len(keys)-1] {
|
||||||
|
value, exists := subtree.values[intermediateKey]
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch node := value.(type) {
|
||||||
|
case *Tree:
|
||||||
|
subtree = node
|
||||||
|
case []*Tree:
|
||||||
|
// go to most recent element
|
||||||
|
if len(node) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
subtree = node[len(node)-1]
|
||||||
|
default:
|
||||||
|
return nil // cannot navigate through other node types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// branch based on final node type
|
||||||
|
switch node := subtree.values[keys[len(keys)-1]].(type) {
|
||||||
|
case *tomlValue:
|
||||||
|
switch n := node.value.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
return getArray(n)
|
||||||
|
default:
|
||||||
|
return node.value
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if homogeneous array, then return slice type object over []interface{}
|
||||||
|
func getArray(n []interface{}) interface{} {
|
||||||
|
var s []string
|
||||||
|
var i64 []int64
|
||||||
|
var f64 []float64
|
||||||
|
var bl []bool
|
||||||
|
for _, value := range n {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case string:
|
||||||
|
s = append(s, v)
|
||||||
|
case int64:
|
||||||
|
i64 = append(i64, v)
|
||||||
|
case float64:
|
||||||
|
f64 = append(f64, v)
|
||||||
|
case bool:
|
||||||
|
bl = append(bl, v)
|
||||||
|
default:
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s) == len(n) {
|
||||||
|
return s
|
||||||
|
} else if len(i64) == len(n) {
|
||||||
|
return i64
|
||||||
|
} else if len(f64) == len(n) {
|
||||||
|
return f64
|
||||||
|
} else if len(bl) == len(n) {
|
||||||
|
return bl
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// GetPosition returns the position of the given key.
|
// GetPosition returns the position of the given key.
|
||||||
func (t *Tree) GetPosition(key string) Position {
|
func (t *Tree) GetPosition(key string) Position {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
@@ -130,6 +213,50 @@ func (t *Tree) GetPosition(key string) Position {
|
|||||||
return t.GetPositionPath(strings.Split(key, "."))
|
return t.GetPositionPath(strings.Split(key, "."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPositionPath sets the position of element in the tree indicated by 'keys'.
|
||||||
|
// If keys is of length zero, the current tree position is set.
|
||||||
|
func (t *Tree) SetPositionPath(keys []string, pos Position) {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
t.position = pos
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subtree := t
|
||||||
|
for _, intermediateKey := range keys[:len(keys)-1] {
|
||||||
|
value, exists := subtree.values[intermediateKey]
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch node := value.(type) {
|
||||||
|
case *Tree:
|
||||||
|
subtree = node
|
||||||
|
case []*Tree:
|
||||||
|
// go to most recent element
|
||||||
|
if len(node) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
subtree = node[len(node)-1]
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// branch based on final node type
|
||||||
|
switch node := subtree.values[keys[len(keys)-1]].(type) {
|
||||||
|
case *tomlValue:
|
||||||
|
node.position = pos
|
||||||
|
return
|
||||||
|
case *Tree:
|
||||||
|
node.position = pos
|
||||||
|
return
|
||||||
|
case []*Tree:
|
||||||
|
// go to most recent element
|
||||||
|
if len(node) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node[len(node)-1].position = pos
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetPositionPath returns the element in the tree indicated by 'keys'.
|
// GetPositionPath returns the element in the tree indicated by 'keys'.
|
||||||
// If keys is of length zero, the current tree is returned.
|
// If keys is of length zero, the current tree is returned.
|
||||||
func (t *Tree) GetPositionPath(keys []string) Position {
|
func (t *Tree) GetPositionPath(keys []string) Position {
|
||||||
@@ -212,7 +339,8 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
|
|||||||
// go to most recent element
|
// go to most recent element
|
||||||
if len(node) == 0 {
|
if len(node) == 0 {
|
||||||
// create element if it does not exist
|
// create element if it does not exist
|
||||||
subtree.values[intermediateKey] = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
|
node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
|
||||||
|
subtree.values[intermediateKey] = node
|
||||||
}
|
}
|
||||||
subtree = node[len(node)-1]
|
subtree = node[len(node)-1]
|
||||||
}
|
}
|
||||||
@@ -232,6 +360,8 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
|
|||||||
toInsert = value
|
toInsert = value
|
||||||
case *tomlValue:
|
case *tomlValue:
|
||||||
v.comment = opts.Comment
|
v.comment = opts.Comment
|
||||||
|
v.commented = opts.Commented
|
||||||
|
v.multiline = opts.Multiline
|
||||||
toInsert = v
|
toInsert = v
|
||||||
default:
|
default:
|
||||||
toInsert = &tomlValue{value: value,
|
toInsert = &tomlValue{value: value,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,6 +40,41 @@ func TestTomlGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTomlGetArray(t *testing.T) {
|
||||||
|
tree, _ := Load(`
|
||||||
|
[test]
|
||||||
|
key = ["one", "two"]
|
||||||
|
key2 = [true, false, false]
|
||||||
|
key3 = [1.5,2.5]
|
||||||
|
`)
|
||||||
|
|
||||||
|
if tree.GetArray("") != tree {
|
||||||
|
t.Errorf("GetArray should return the tree itself when given an empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
expect := []string{"one", "two"}
|
||||||
|
actual := tree.GetArray("test.key").([]string)
|
||||||
|
if !reflect.DeepEqual(actual, expect) {
|
||||||
|
t.Errorf("GetArray should return the []string value")
|
||||||
|
}
|
||||||
|
|
||||||
|
expect2 := []bool{true, false, false}
|
||||||
|
actual2 := tree.GetArray("test.key2").([]bool)
|
||||||
|
if !reflect.DeepEqual(actual2, expect2) {
|
||||||
|
t.Errorf("GetArray should return the []bool value")
|
||||||
|
}
|
||||||
|
|
||||||
|
expect3 := []float64{1.5, 2.5}
|
||||||
|
actual3 := tree.GetArray("test.key3").([]float64)
|
||||||
|
if !reflect.DeepEqual(actual3, expect3) {
|
||||||
|
t.Errorf("GetArray should return the []float64 value")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tree.GetArray(`\`) != nil {
|
||||||
|
t.Errorf("should return nil when the key is malformed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTomlGetDefault(t *testing.T) {
|
func TestTomlGetDefault(t *testing.T) {
|
||||||
tree, _ := Load(`
|
tree, _ := Load(`
|
||||||
[test]
|
[test]
|
||||||
@@ -148,6 +184,51 @@ func TestTomlGetPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTomlGetArrayPath(t *testing.T) {
|
||||||
|
for idx, item := range []struct {
|
||||||
|
Name string
|
||||||
|
Path []string
|
||||||
|
Make func() (tree *Tree, expected interface{})
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "empty",
|
||||||
|
Path: []string{},
|
||||||
|
Make: func() (tree *Tree, expected interface{}) {
|
||||||
|
tree = newTree()
|
||||||
|
expected = tree
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "int64",
|
||||||
|
Path: []string{"a"},
|
||||||
|
Make: func() (tree *Tree, expected interface{}) {
|
||||||
|
var err error
|
||||||
|
tree, err = Load(`a = [1,2,3]`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
expected = []int64{1, 2, 3}
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(item.Name, func(t *testing.T) {
|
||||||
|
tree, expected := item.Make()
|
||||||
|
result := tree.GetArrayPath(item.Path)
|
||||||
|
if !reflect.DeepEqual(result, expected) {
|
||||||
|
t.Errorf("GetArrayPath[%d] %v - expected %#v, got %#v instead.", idx, item.Path, expected, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tree, _ := Load("[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6")
|
||||||
|
if tree.GetArrayPath([]string{"whatever"}) != nil {
|
||||||
|
t.Error("GetArrayPath should return nil when the key does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestTomlFromMap(t *testing.T) {
|
func TestTomlFromMap(t *testing.T) {
|
||||||
simpleMap := map[string]interface{}{"hello": 42}
|
simpleMap := map[string]interface{}{"hello": 42}
|
||||||
tree, err := TreeFromMap(simpleMap)
|
tree, err := TreeFromMap(simpleMap)
|
||||||
|
|||||||
@@ -57,6 +57,19 @@ func simpleValueCoercion(object interface{}) (interface{}, error) {
|
|||||||
return float64(original), nil
|
return float64(original), nil
|
||||||
case fmt.Stringer:
|
case fmt.Stringer:
|
||||||
return original.String(), nil
|
return original.String(), nil
|
||||||
|
case []interface{}:
|
||||||
|
value := reflect.ValueOf(original)
|
||||||
|
length := value.Len()
|
||||||
|
arrayValue := reflect.MakeSlice(value.Type(), 0, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
val := value.Index(i).Interface()
|
||||||
|
simpleValue, err := simpleValueCoercion(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue))
|
||||||
|
}
|
||||||
|
return arrayValue.Interface(), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("cannot convert type %T to Tree", object)
|
return nil, fmt.Errorf("cannot convert type %T to Tree", object)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package toml
|
package toml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -124,3 +125,119 @@ func TestRoundTripArrayOfTables(t *testing.T) {
|
|||||||
t.Errorf("want:\n%s\ngot:\n%s", want, got)
|
t.Errorf("want:\n%s\ngot:\n%s", want, got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTomlSliceOfSlice(t *testing.T) {
|
||||||
|
tree, err := Load(` hosts=[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ] `)
|
||||||
|
m := tree.ToMap()
|
||||||
|
tree, err = TreeFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
type Struct struct {
|
||||||
|
Hosts [][]string
|
||||||
|
}
|
||||||
|
var actual Struct
|
||||||
|
tree.Unmarshal(&actual)
|
||||||
|
|
||||||
|
expected := Struct{Hosts: [][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlSliceOfSliceOfSlice(t *testing.T) {
|
||||||
|
tree, err := Load(` hosts=[[["10.1.0.107:9092","10.1.0.107:9093", "192.168.0.40:9094"] ]] `)
|
||||||
|
m := tree.ToMap()
|
||||||
|
tree, err = TreeFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
type Struct struct {
|
||||||
|
Hosts [][][]string
|
||||||
|
}
|
||||||
|
var actual Struct
|
||||||
|
tree.Unmarshal(&actual)
|
||||||
|
|
||||||
|
expected := Struct{Hosts: [][][]string{[][]string{[]string{"10.1.0.107:9092", "10.1.0.107:9093", "192.168.0.40:9094"}}}}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlSliceOfSliceInt(t *testing.T) {
|
||||||
|
tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `)
|
||||||
|
m := tree.ToMap()
|
||||||
|
tree, err = TreeFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
type Struct struct {
|
||||||
|
Hosts [][]int
|
||||||
|
}
|
||||||
|
var actual Struct
|
||||||
|
err = tree.Unmarshal(&actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := Struct{Hosts: [][]int{[]int{1, 2, 3}, []int{4, 5, 6}}}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestTomlSliceOfSliceInt64(t *testing.T) {
|
||||||
|
tree, err := Load(` hosts=[[1,2,3],[4,5,6] ] `)
|
||||||
|
m := tree.ToMap()
|
||||||
|
tree, err = TreeFromMap(m)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
type Struct struct {
|
||||||
|
Hosts [][]int64
|
||||||
|
}
|
||||||
|
var actual Struct
|
||||||
|
err = tree.Unmarshal(&actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}, []int64{4, 5, 6}}}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlSliceOfSliceInt64FromMap(t *testing.T) {
|
||||||
|
tree, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{int32(1), int8(2), 3}}})
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
type Struct struct {
|
||||||
|
Hosts [][]int64
|
||||||
|
}
|
||||||
|
var actual Struct
|
||||||
|
err = tree.Unmarshal(&actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should not error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := Struct{Hosts: [][]int64{[]int64{1, 2, 3}}}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Bad unmarshal: expected %+v, got %+v", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
func TestTomlSliceOfSliceError(t *testing.T) { // make Codecov happy
|
||||||
|
_, err := TreeFromMap(map[string]interface{}{"hosts": [][]interface{}{[]interface{}{1, 2, []struct{}{}}}})
|
||||||
|
expected := "cannot convert type []struct {} to Tree"
|
||||||
|
if err.Error() != expected {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -163,7 +163,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
|
|||||||
return "\"" + encodeTomlString(value) + "\"", nil
|
return "\"" + encodeTomlString(value) + "\"", nil
|
||||||
case []byte:
|
case []byte:
|
||||||
b, _ := v.([]byte)
|
b, _ := v.([]byte)
|
||||||
return tomlValueStringRepresentation(string(b), commented, indent, ord, arraysOneElementPerLine)
|
return string(b), nil
|
||||||
case bool:
|
case bool:
|
||||||
if value {
|
if value {
|
||||||
return "true", nil
|
return "true", nil
|
||||||
|
|||||||
Reference in New Issue
Block a user