Compare commits

..

16 Commits

Author SHA1 Message Date
Allen 8e8d2a6aad Support unmarshal into toml.Tree (#347)
Fixes #333
2020-04-03 07:10:45 -04:00
Thomas Pelletier 3f7178ffd6 CI computes manifest for built binaries (#346) 2020-03-30 11:59:45 -04:00
Thomas Pelletier 9fd5922321 Empty commit to trigger release 2020-03-30 10:59:59 -04:00
Thomas Pelletier 610cf85ed6 CI Build Binaries (#345) 2020-03-30 10:51:26 -04:00
Thomas Pelletier 99f8a2a010 Fix codecov on pull requests (#344) 2020-03-25 13:21:25 -04:00
Thomas Pelletier 556d384d4c Bump go version to 1.14 and 1.13 (#343) 2020-03-25 12:52:59 -04:00
Allen eb7280e4a7 Add interface{} support (#341) 2020-03-25 11:12:18 -04:00
Allen 7ee1118b4b Fix unmarshaling of nested structs (#340)
Fixes #339
2020-03-23 11:23:21 -04:00
Allen a12e102214 Fix multiline + non-primitive commenting (#336)
Fixes #216
2020-03-16 22:51:47 -04:00
jinleiw ad60b7e437 Add support for nested interface{} unmarshal (#335)
Co-authored-by: jlwang <jlwang@sysnew.com>

Fixes #331
2020-03-16 10:38:53 -04:00
Allen 3503483c73 Fix unexpected token type in inline table (#334)
Fixes #321
2020-03-10 13:39:48 -04:00
Thomas Pelletier d2d17bccec Update CI vm images versions (#328)
Received an email from Microsoft stating that those versions will be
discontinued. Switching to use -latest for all of them to not be
bothered with that in the future.
2020-01-24 12:32:53 -05:00
dependabot-preview[bot] 76a94674c9 Bump gopkg.in/yaml.v2 from 2.2.7 to 2.2.8 (#327)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.7 to 2.2.8.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.7...v2.2.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-01-23 11:32:53 -05:00
Nicolas Bedos 80f8b7660b Support default values for inner structs (#326) 2020-01-13 09:39:27 -05:00
dependabot-preview[bot] 6f6ca41621 Bump gopkg.in/yaml.v2 from 2.2.5 to 2.2.7 (#324)
Bumps [gopkg.in/yaml.v2](https://github.com/go-yaml/yaml) from 2.2.5 to 2.2.7.
- [Release notes](https://github.com/go-yaml/yaml/releases)
- [Commits](https://github.com/go-yaml/yaml/compare/v2.2.5...v2.2.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-11-20 21:21:26 -05:00
dependabot-preview[bot] c4efb7477c Bump gopkg.in/yaml.v2 from 2.2.4 to 2.2.5 (#322) 2019-11-06 15:49:39 +00:00
10 changed files with 920 additions and 89 deletions
+29
View File
@@ -0,0 +1,29 @@
export CGO_ENABLED=0
go := go
go.goos ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f1)
go.goarch ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f2)
out.tools := tomll tomljson jsontoml
out.dist := $(out.tools:=_$(go.goos)_$(go.goarch).tar.xz)
sources := $(wildcard **/*.go)
.PHONY:
tools: $(out.tools)
$(out.tools): $(sources)
GOOS=$(go.goos) GOARCH=$(go.goarch) $(go) build ./cmd/$@
.PHONY:
dist: $(out.dist)
$(out.dist):%_$(go.goos)_$(go.goarch).tar.xz: %
if [ "$(go.goos)" = "windows" ]; then \
tar -cJf $@ $^.exe; \
else \
tar -cJf $@ $^; \
fi
.PHONY:
clean:
rm -rf $(out.tools) $(out.dist)
+86 -23
View File
@@ -13,9 +13,9 @@ stages:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.13"
displayName: "Install Go 1.14"
inputs:
version: "1.13"
version: "1.14"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- script: mkdir -p ${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
steps:
- task: GoTool@0
displayName: "Install Go 1.13"
displayName: "Install Go 1.14"
inputs:
version: "1.13"
version: "1.14"
- task: Go@0
displayName: "go fmt ./..."
inputs:
@@ -51,9 +51,9 @@ stages:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.13"
displayName: "Install Go 1.14"
inputs:
version: "1.13"
version: "1.14"
- task: Go@0
displayName: "Generate coverage"
inputs:
@@ -62,16 +62,18 @@ stages:
- task: Bash@3
inputs:
targetType: 'inline'
script: 'bash <(curl -s https://codecov.io/bash) -t $(CODECOV_TOKEN)'
script: 'bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}'
env:
CODECOV_TOKEN: $(CODECOV_TOKEN)
- job: benchmark
displayName: "benchmark"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.13"
displayName: "Install Go 1.14"
inputs:
version: "1.13"
version: "1.14"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- task: Bash@3
inputs:
@@ -84,9 +86,9 @@ stages:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.13"
displayName: "Install Go 1.14"
inputs:
version: "1.13"
version: "1.14"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
@@ -100,24 +102,24 @@ stages:
displayName: "unit tests"
strategy:
matrix:
linux 1.14:
goVersion: '1.14'
imageName: 'ubuntu-latest'
mac 1.14:
goVersion: '1.14'
imageName: 'macOS-latest'
windows 1.14:
goVersion: '1.14'
imageName: 'windows-latest'
linux 1.13:
goVersion: '1.13'
imageName: 'ubuntu-latest'
mac 1.13:
goVersion: '1.13'
imageName: 'macos-10.13'
imageName: 'macOS-latest'
windows 1.13:
goVersion: '1.13'
imageName: 'vs2017-win2016'
linux 1.12:
goVersion: '1.12'
imageName: 'ubuntu-latest'
mac 1.12:
goVersion: '1.12'
imageName: 'macos-10.13'
windows 1.12:
goVersion: '1.12'
imageName: 'vs2017-win2016'
imageName: 'windows-latest'
pool:
vmImage: $(imageName)
steps:
@@ -130,6 +132,67 @@ stages:
inputs:
command: 'test'
arguments: './...'
- stage: build_binaries
displayName: "Build binaries"
dependsOn: run_checks
jobs:
- job: build_binary
displayName: "Build binary"
strategy:
matrix:
linux_amd64:
GOOS: linux
GOARCH: amd64
darwin_amd64:
GOOS: darwin
GOARCH: amd64
windows_amd64:
GOOS: windows
GOARCH: amd64
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go"
inputs:
version: 1.14
- task: Bash@3
inputs:
targetType: inline
script: "make dist"
env:
go.goos: $(GOOS)
go.goarch: $(GOARCH)
- task: CopyFiles@2
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '*.tar.xz'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: binaries
- stage: build_binaries_manifest
displayName: "Build binaries manifest"
dependsOn: build_binaries
jobs:
- job: build_manifest
displayName: "Build binaries manifest"
steps:
- task: DownloadBuildArtifacts@0
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'binaries'
downloadPath: '$(Build.SourcesDirectory)'
- task: Bash@3
inputs:
targetType: inline
script: "cd binaries && sha256sum --binary *.tar.xz | tee $(Build.ArtifactStagingDirectory)/sha256sums.txt"
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: manifest
- stage: build_docker_image
displayName: "Build Docker image"
@@ -164,4 +227,4 @@ stages:
command: 'buildAndPush'
Dockerfile: 'Dockerfile'
buildContext: '.'
tags: 'latest'
tags: 'latest'
+1 -1
View File
@@ -5,5 +5,5 @@ go 1.12
require (
github.com/BurntSushi/toml v0.3.1
github.com/davecgh/go-spew v1.1.1
gopkg.in/yaml.v2 v2.2.4
gopkg.in/yaml.v2 v2.2.8
)
+6
View File
@@ -9,3 +9,9 @@ gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+1 -1
View File
@@ -256,7 +256,7 @@ func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn {
func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn {
l.next()
l.emit(tokenRightCurlyBrace)
return l.lexVoid
return l.lexRvalue
}
func (l *tomlLexer) lexDate() tomlLexStateFn {
+95 -37
View File
@@ -302,7 +302,7 @@ func (e *Encoder) marshal(v interface{}) ([]byte, error) {
}
var buf bytes.Buffer
_, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order)
_, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, false)
return buf.Bytes(), err
}
@@ -320,20 +320,25 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
tval := e.nextTree()
switch mtype.Kind() {
case reflect.Struct:
for i := 0; i < mtype.NumField(); i++ {
mtypef, mvalf := mtype.Field(i), mval.Field(i)
opts := tomlOptions(mtypef, e.annotation)
if opts.include && (!opts.omitempty || !isZero(mvalf)) {
val, err := e.valueToToml(mtypef.Type, mvalf)
if err != nil {
return nil, err
}
switch mval.Interface().(type) {
case Tree:
reflect.ValueOf(tval).Elem().Set(mval)
default:
for i := 0; i < mtype.NumField(); i++ {
mtypef, mvalf := mtype.Field(i), mval.Field(i)
opts := tomlOptions(mtypef, e.annotation)
if opts.include && ((mtypef.Type.Kind() != reflect.Interface && !opts.omitempty) || !isZero(mvalf)) {
val, err := e.valueToToml(mtypef.Type, mvalf)
if err != nil {
return nil, err
}
tval.SetWithOptions(opts.name, SetOptions{
Comment: opts.comment,
Commented: opts.commented,
Multiline: opts.multiline,
}, val)
tval.SetWithOptions(opts.name, SetOptions{
Comment: opts.comment,
Commented: opts.commented,
Multiline: opts.multiline,
}, val)
}
}
}
case reflect.Map:
@@ -358,12 +363,15 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
}
for _, key := range keys {
mvalf := mval.MapIndex(key)
if (mtype.Elem().Kind() == reflect.Ptr || mtype.Elem().Kind() == reflect.Interface) && mvalf.IsNil() {
continue
}
val, err := e.valueToToml(mtype.Elem(), mvalf)
if err != nil {
return nil, err
}
if e.quoteMapKeys {
keyStr, err := tomlValueStringRepresentation(key.String(), "", e.arraysOneElementPerLine)
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.arraysOneElementPerLine)
if err != nil {
return nil, err
}
@@ -391,6 +399,9 @@ func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*T
// Convert given marshal slice to slice of toml values
func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
if mtype.Elem().Kind() == reflect.Interface {
return nil, fmt.Errorf("marshal can't handle []interface{}")
}
tval := make([]interface{}, mval.Len(), mval.Len())
for i := 0; i < mval.Len(); i++ {
val, err := e.valueToToml(mtype.Elem(), mval.Index(i))
@@ -408,6 +419,9 @@ func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface
if mtype.Kind() == reflect.Ptr {
return e.valueToToml(mtype.Elem(), mval.Elem())
}
if mtype.Kind() == reflect.Interface {
return e.valueToToml(mval.Elem().Type(), mval.Elem())
}
switch {
case isCustomMarshaler(mtype):
return callCustomMarshaler(mval)
@@ -561,11 +575,17 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
mval = reflect.New(mtype).Elem()
}
for i := 0; i < mtype.NumField(); i++ {
mtypef := mtype.Field(i)
an := annotation{tag: d.tagName}
opts := tomlOptions(mtypef, an)
if opts.include {
switch mval.Interface().(type) {
case Tree:
mval.Set(reflect.ValueOf(tval).Elem())
default:
for i := 0; i < mtype.NumField(); i++ {
mtypef := mtype.Field(i)
an := annotation{tag: d.tagName}
opts := tomlOptions(mtypef, an)
if !opts.include {
continue
}
baseKey := opts.name
keysToTry := []string{
baseKey,
@@ -575,20 +595,22 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
}
found := false
for _, key := range keysToTry {
exists := tval.Has(key)
if !exists {
continue
if tval != nil {
for _, key := range keysToTry {
exists := tval.Has(key)
if !exists {
continue
}
val := tval.Get(key)
fval := mval.Field(i)
mvalf, err := d.valueFromToml(mtypef.Type, val, &fval)
if err != nil {
return mval, formatError(err, tval.GetPosition(key))
}
mval.Field(i).Set(mvalf)
found = true
break
}
val := tval.Get(key)
fval := mval.Field(i)
mvalf, err := d.valueFromToml(mtypef.Type, val, &fval)
if err != nil {
return mval, formatError(err, tval.GetPosition(key))
}
mval.Field(i).Set(mvalf)
found = true
break
}
if !found && opts.defaultValue != "" {
@@ -624,9 +646,13 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
mval.Field(i).Set(reflect.ValueOf(val))
}
// save the old behavior above and try to check anonymous structs
if !found && opts.defaultValue == "" && mtypef.Anonymous && mtypef.Type.Kind() == reflect.Struct {
v, err := d.valueFromTree(mtypef.Type, tval, nil)
// save the old behavior above and try to check structs
if !found && opts.defaultValue == "" && mtypef.Type.Kind() == reflect.Struct {
tmpTval := tval
if !mtypef.Anonymous {
tmpTval = nil
}
v, err := d.valueFromTree(mtypef.Type, tmpTval, nil)
if err != nil {
return v, err
}
@@ -692,16 +718,41 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
if isTree(mtype) {
return d.valueFromTree(mtype, t, mval11)
}
if mtype.Kind() == reflect.Interface {
if mval1 == nil || mval1.IsNil() {
return d.valueFromTree(reflect.TypeOf(map[string]interface{}{}), t, nil)
} else {
return d.valueFromToml(mval1.Elem().Type(), t, nil)
}
}
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval)
case []*Tree:
if isTreeSequence(mtype) {
return d.valueFromTreeSlice(mtype, t)
}
if mtype.Kind() == reflect.Interface {
if mval1 == nil || mval1.IsNil() {
return d.valueFromTreeSlice(reflect.TypeOf([]map[string]interface{}{}), t)
} else {
ival := mval1.Elem()
return d.valueFromToml(mval1.Elem().Type(), t, &ival)
}
}
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval)
case []interface{}:
if isOtherSequence(mtype) {
return d.valueFromOtherSlice(mtype, t)
}
if mtype.Kind() == reflect.Interface {
if mval1 == nil || mval1.IsNil() {
return d.valueFromOtherSlice(reflect.TypeOf([]interface{}{}), t)
} else {
ival := mval1.Elem()
return d.valueFromToml(mval1.Elem().Type(), t, &ival)
}
}
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval)
default:
switch mtype.Kind() {
@@ -786,6 +837,13 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
}
return val.Convert(mtype), nil
case reflect.Interface:
if mval1 == nil || mval1.IsNil() {
return reflect.ValueOf(tval), nil
} else {
ival := mval1.Elem()
return d.valueFromToml(mval1.Elem().Type(), t, &ival)
}
default:
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind())
}
@@ -795,7 +853,7 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) {
var melem *reflect.Value
if mval1 != nil && !mval1.IsNil() && mtype.Elem().Kind() == reflect.Struct {
if mval1 != nil && !mval1.IsNil() && (mtype.Elem().Kind() == reflect.Struct || mtype.Elem().Kind() == reflect.Interface) {
elem := mval1.Elem()
melem = &elem
}
+637 -5
View File
@@ -157,6 +157,44 @@ var mashalOrderPreserveMapToml = []byte(`title = "TOML Marshal Testing"
j9 = "10"
`)
type Conf struct {
Name string
Age int
Inter interface{}
}
type NestedStruct struct {
FirstName string
LastName string
Age int
}
var doc = []byte(`Name = "rui"
Age = 18
[Inter]
FirstName = "wang"
LastName = "jl"
Age = 100`)
func TestInterface(t *testing.T) {
var config Conf
config.Inter = &NestedStruct{}
err := Unmarshal(doc, &config)
expected := Conf{
Name: "rui",
Age: 18,
Inter: &NestedStruct{
FirstName: "wang",
LastName: "jl",
Age: 100,
},
}
if err != nil || !reflect.DeepEqual(config, expected) {
t.Errorf("Bad unmarshal: expected %v, got %v", expected, config)
}
}
func TestBasicMarshal(t *testing.T) {
result, err := Marshal(basicTestData)
if err != nil {
@@ -913,6 +951,213 @@ func TestMarshalComment(t *testing.T) {
}
}
func TestMarshalMultilineCommented(t *testing.T) {
expectedToml := []byte(`# MultilineArray = [
# 100,
# 200,
# 300,
# ]
# MultilineNestedArray = [
# [
# "a",
# "b",
# "c",
# ],
# [
# "d",
# "e",
# "f",
# ],
# ]
# MultilineString = """
# I
# am
# Allen"""
NonCommented = "Not commented line"
`)
type StructWithMultiline struct {
NonCommented string
MultilineString string `commented:"true" multiline:"true"`
MultilineArray []int `commented:"true"`
MultilineNestedArray [][]string `commented:"true"`
}
var buf bytes.Buffer
enc := NewEncoder(&buf)
if err := enc.ArraysWithOneElementPerLine(true).Encode(StructWithMultiline{
NonCommented: "Not commented line",
MultilineString: "I\nam\nAllen",
MultilineArray: []int{100, 200, 300},
MultilineNestedArray: [][]string{
{"a", "b", "c"},
{"d", "e", "f"},
},
}); err == nil {
result := buf.Bytes()
if !bytes.Equal(result, expectedToml) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result)
}
} else {
t.Fatal(err)
}
}
func TestMarshalNonPrimitiveTypeCommented(t *testing.T) {
expectedToml := []byte(`
# [CommentedMapField]
# [CommentedMapField.CommentedMapField1]
# SingleLineString = "This line should be commented out"
# [CommentedMapField.CommentedMapField2]
# SingleLineString = "This line should be commented out"
# [CommentedStructField]
# [CommentedStructField.CommentedStructField]
# MultilineArray = [
# 1,
# 2,
# ]
# MultilineNestedArray = [
# [
# 10,
# 20,
# ],
# [
# 100,
# 200,
# ],
# ]
# MultilineString = """
# This line
# should be
# commented out"""
# [CommentedStructField.NotCommentedStructField]
# MultilineArray = [
# 1,
# 2,
# ]
# MultilineNestedArray = [
# [
# 10,
# 20,
# ],
# [
# 100,
# 200,
# ],
# ]
# MultilineString = """
# This line
# should be
# commented out"""
[NotCommentedStructField]
# [NotCommentedStructField.CommentedStructField]
# MultilineArray = [
# 1,
# 2,
# ]
# MultilineNestedArray = [
# [
# 10,
# 20,
# ],
# [
# 100,
# 200,
# ],
# ]
# MultilineString = """
# This line
# should be
# commented out"""
[NotCommentedStructField.NotCommentedStructField]
MultilineArray = [
3,
4,
]
MultilineNestedArray = [
[
30,
40,
],
[
300,
400,
],
]
MultilineString = """
This line
should NOT be
commented out"""
`)
type InnerStruct struct {
MultilineString string `multiline:"true"`
MultilineArray []int
MultilineNestedArray [][]int
}
type MiddleStruct struct {
NotCommentedStructField InnerStruct
CommentedStructField InnerStruct `commented:"true"`
}
type OuterStruct struct {
CommentedStructField MiddleStruct `commented:"true"`
NotCommentedStructField MiddleStruct
CommentedMapField map[string]struct{ SingleLineString string } `commented:"true"`
}
commentedTestStruct := OuterStruct{
CommentedStructField: MiddleStruct{
NotCommentedStructField: InnerStruct{
MultilineString: "This line\nshould be\ncommented out",
MultilineArray: []int{1, 2},
MultilineNestedArray: [][]int{{10, 20}, {100, 200}},
},
CommentedStructField: InnerStruct{
MultilineString: "This line\nshould be\ncommented out",
MultilineArray: []int{1, 2},
MultilineNestedArray: [][]int{{10, 20}, {100, 200}},
},
},
NotCommentedStructField: MiddleStruct{
NotCommentedStructField: InnerStruct{
MultilineString: "This line\nshould NOT be\ncommented out",
MultilineArray: []int{3, 4},
MultilineNestedArray: [][]int{{30, 40}, {300, 400}},
},
CommentedStructField: InnerStruct{
MultilineString: "This line\nshould be\ncommented out",
MultilineArray: []int{1, 2},
MultilineNestedArray: [][]int{{10, 20}, {100, 200}},
},
},
CommentedMapField: map[string]struct{ SingleLineString string }{
"CommentedMapField1": {
SingleLineString: "This line should be commented out",
},
"CommentedMapField2": {
SingleLineString: "This line should be commented out",
},
},
}
var buf bytes.Buffer
enc := NewEncoder(&buf)
if err := enc.ArraysWithOneElementPerLine(true).Encode(commentedTestStruct); err == nil {
result := buf.Bytes()
if !bytes.Equal(result, expectedToml) {
t.Errorf("Bad marshal: expected\n-----\n%s\n-----\ngot\n-----\n%s\n-----\n", expectedToml, result)
}
} else {
t.Fatal(err)
}
}
type mapsTestStruct struct {
Simple map[string]string
Paths map[string]string
@@ -1199,6 +1444,54 @@ func TestMarshalCustomMultiline(t *testing.T) {
}
}
func TestMarshalEmbedTree(t *testing.T) {
expected := []byte(`OuterField1 = "Out"
OuterField2 = 1024
[TreeField]
InnerField1 = "In"
InnerField2 = 2048
[TreeField.EmbedStruct]
EmbedField = "Embed"
`)
type InnerStruct struct {
InnerField1 string
InnerField2 int
EmbedStruct struct{
EmbedField string
}
}
type OuterStruct struct {
OuterField1 string
OuterField2 int
TreeField *Tree
}
tree, err := Load(`
InnerField1 = "In"
InnerField2 = 2048
[EmbedStruct]
EmbedField = "Embed"
`)
if err != nil {
t.Fatal(err)
}
out := OuterStruct{
"Out",
1024,
tree,
}
actual, _ := Marshal(out)
if !bytes.Equal(actual, expected){
t.Errorf("Bad marshal: expected %s, got %s", expected, actual)
}
}
var testDocBasicToml = []byte(`
[document]
bool_val = true
@@ -1483,12 +1776,20 @@ func TestUnmarshalCamelCaseKey(t *testing.T) {
}
func TestUnmarshalDefault(t *testing.T) {
type EmbeddedStruct struct {
StringField string `default:"c"`
}
var doc struct {
StringField string `default:"a"`
BoolField bool `default:"true"`
IntField int `default:"1"`
Int64Field int64 `default:"2"`
Float64Field float64 `default:"3.1"`
StringField string `default:"a"`
BoolField bool `default:"true"`
IntField int `default:"1"`
Int64Field int64 `default:"2"`
Float64Field float64 `default:"3.1"`
NonEmbeddedStruct struct {
StringField string `default:"b"`
}
EmbeddedStruct
}
err := Unmarshal([]byte(``), &doc)
@@ -1510,6 +1811,12 @@ func TestUnmarshalDefault(t *testing.T) {
if doc.Float64Field != 3.1 {
t.Errorf("Float64Field should be 3.1, not %f", doc.Float64Field)
}
if doc.NonEmbeddedStruct.StringField != "b" {
t.Errorf("StringField should be \"b\", not %s", doc.NonEmbeddedStruct.StringField)
}
if doc.EmbeddedStruct.StringField != "c" {
t.Errorf("StringField should be \"c\", not %s", doc.EmbeddedStruct.StringField)
}
}
func TestUnmarshalDefaultFailureBool(t *testing.T) {
@@ -2140,3 +2447,328 @@ func TestMarshalLocalTime(t *testing.T) {
})
}
}
// test case for issue #339
func TestUnmarshalSameInnerField(t *testing.T) {
type InterStruct2 struct {
Test string
Name string
Age int
}
type Inter2 struct {
Name string
Age int
InterStruct2 InterStruct2
}
type Server struct {
Name string `toml:"name"`
Inter2 Inter2 `toml:"inter2"`
}
var server Server
if err := Unmarshal([]byte(`name = "123"
[inter2]
name = "inter2"
age = 222`), &server); err == nil {
expected := Server{
Name: "123",
Inter2: Inter2{
Name: "inter2",
Age: 222,
},
}
if !reflect.DeepEqual(server, expected) {
t.Errorf("Bad unmarshal: expected %v, got %v", expected, server)
}
} else {
t.Fatalf("unexpected error: %v", err)
}
}
func TestMarshalInterface(t *testing.T) {
type InnerStruct struct {
InnerField string
}
type OuterStruct struct {
PrimitiveField interface{}
ArrayField interface{}
StructArrayField interface{}
MapField map[string]interface{}
StructField interface{}
PointerField interface{}
NilField interface{}
InterfacePointerField *interface{}
}
type ShouldNotSupportStruct struct {
InterfaceArray []interface{}
}
expected := []byte(`ArrayField = [1,2,3]
InterfacePointerField = "hello world"
PrimitiveField = "string"
[MapField]
key1 = "value1"
key2 = false
[MapField.key3]
InnerField = "value3"
[PointerField]
InnerField = "yyy"
[[StructArrayField]]
InnerField = "s1"
[[StructArrayField]]
InnerField = "s2"
[StructField]
InnerField = "xxx"
`)
var h interface{} = "hello world"
if result, err := Marshal(OuterStruct{
"string",
[]int{1, 2, 3},
[]InnerStruct{{"s1"}, {"s2"}},
map[string]interface{}{
"key1": "value1",
"key2": false,
"key3": InnerStruct{"value3"},
"nil value": nil,
},
InnerStruct{
"xxx",
},
&InnerStruct{
"yyy",
},
nil,
&h,
}); err == nil {
if !bytes.Equal(result, expected) {
t.Errorf("Bad marshal: expected\n----\n%s\n----\ngot\n----\n%s\n----\n", expected, result)
}
} else {
t.Fatal(err)
}
// according to the toml standard, data types of array may not be mixed
if _, err := Marshal(ShouldNotSupportStruct{[]interface{}{1, "a", true}}); err == nil {
t.Errorf("Should not support []interface{} marshaling")
}
}
func TestUnmarshalToNilInterface(t *testing.T) {
toml := []byte(`
PrimitiveField = "Hello"
ArrayField = [1,2,3]
InterfacePointerField = "World"
[StructField]
Field1 = 123
Field2 = "Field2"
[MapField]
MapField1 = [4,5,6]
MapField2 = {A = "A"}
MapField3 = false
[[StructArrayField]]
Name = "Allen"
Age = 20
[[StructArrayField]]
Name = "Jack"
Age = 23
`)
type OuterStruct struct {
PrimitiveField interface{}
ArrayField interface{}
StructArrayField interface{}
MapField map[string]interface{}
StructField interface{}
NilField interface{}
InterfacePointerField *interface{}
}
var s interface{} = "World"
expected := OuterStruct{
PrimitiveField: "Hello",
ArrayField: []interface{}{int64(1), int64(2), int64(3)},
StructField: map[string]interface{}{
"Field1": int64(123),
"Field2": "Field2",
},
MapField: map[string]interface{}{
"MapField1": []interface{}{int64(4), int64(5), int64(6)},
"MapField2": map[string]interface{}{
"A": "A",
},
"MapField3": false,
},
NilField: nil,
InterfacePointerField: &s,
StructArrayField: []map[string]interface{}{
{
"Name": "Allen",
"Age": int64(20),
},
{
"Name": "Jack",
"Age": int64(23),
},
},
}
actual := OuterStruct{}
if err := Unmarshal(toml, &actual); err == nil {
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual)
}
} else {
t.Fatal(err)
}
}
func TestUnmarshalToNonNilInterface(t *testing.T) {
toml := []byte(`
PrimitiveField = "Allen"
ArrayField = [1,2,3]
[StructField]
InnerField = "After1"
[PointerField]
InnerField = "After2"
[InterfacePointerField]
InnerField = "After"
[MapField]
MapField1 = [4,5,6]
MapField2 = {A = "A"}
MapField3 = false
[[StructArrayField]]
InnerField = "After3"
[[StructArrayField]]
InnerField = "After4"
`)
type InnerStruct struct {
InnerField interface{}
}
type OuterStruct struct {
PrimitiveField interface{}
ArrayField interface{}
StructArrayField interface{}
MapField map[string]interface{}
StructField interface{}
PointerField interface{}
NilField interface{}
InterfacePointerField *interface{}
}
var s interface{} = InnerStruct{"After"}
expected := OuterStruct{
PrimitiveField: "Allen",
ArrayField: []int{1, 2, 3},
StructField: InnerStruct{InnerField: "After1"},
MapField: map[string]interface{}{
"MapField1": []interface{}{int64(4), int64(5), int64(6)},
"MapField2": map[string]interface{}{
"A": "A",
},
"MapField3": false,
},
PointerField: &InnerStruct{InnerField: "After2"},
NilField: nil,
InterfacePointerField: &s,
StructArrayField: []InnerStruct{
{InnerField: "After3"},
{InnerField: "After4"},
},
}
actual := OuterStruct{
PrimitiveField: "aaa",
ArrayField: []int{100, 200, 300, 400},
StructField: InnerStruct{InnerField: "Before1"},
MapField: map[string]interface{}{
"MapField1": []int{4, 5, 6},
"MapField2": map[string]string{
"B": "BBB",
},
"MapField3": true,
},
PointerField: &InnerStruct{InnerField: "Before2"},
NilField: nil,
InterfacePointerField: &s,
StructArrayField: []InnerStruct{
{InnerField: "Before3"},
{InnerField: "Before4"},
},
}
if err := Unmarshal(toml, &actual); err == nil {
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual)
}
} else {
t.Fatal(err)
}
}
func TestUnmarshalEmbedTree(t *testing.T) {
toml := []byte(`
OuterField1 = "Out"
OuterField2 = 1024
[TreeField]
InnerField1 = "In"
InnerField2 = 2048
[TreeField.EmbedStruct]
EmbedField = "Embed"
`)
type InnerStruct struct {
InnerField1 string
InnerField2 int
EmbedStruct struct{
EmbedField string
}
}
type OuterStruct struct {
OuterField1 string
OuterField2 int
TreeField *Tree
}
out := OuterStruct{}
actual := InnerStruct{}
expected := InnerStruct{
"In",
2048,
struct{
EmbedField string
}{
EmbedField:"Embed",
},
}
if err := Unmarshal(toml, &out); err != nil {
t.Fatal(err)
}
if err := out.TreeField.Unmarshal(&actual); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(actual, expected){
t.Errorf("Bad unmarshal: expected %v, got %v", expected, actual)
}
}
+34 -1
View File
@@ -581,6 +581,39 @@ func TestDoubleInlineGroup(t *testing.T) {
})
}
func TestNestedInlineGroup(t *testing.T) {
tree, err := Load("out = {block0 = {x = 99, y = 100}, block1 = {p = \"999\", q = \"1000\"}}")
assertTree(t, tree, err, map[string]interface{}{
"out": map[string]interface{}{
"block0": map[string]interface{}{
"x": int64(99),
"y": int64(100),
},
"block1": map[string]interface{}{
"p": "999",
"q": "1000",
},
},
})
}
func TestArrayInNestedInlineGroup(t *testing.T) {
tree, err := Load(`image = {name = "xxx", palette = {id = 100, colors = ["red", "blue", "green"]}}`)
assertTree(t, tree, err, map[string]interface{}{
"image": map[string]interface{}{
"name": "xxx",
"palette": map[string]interface{}{
"id": int64(100),
"colors": []string{
"red",
"blue",
"green",
},
},
},
})
}
func TestExampleInlineGroup(t *testing.T) {
tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }`)
@@ -864,7 +897,7 @@ func TestTomlValueStringRepresentation(t *testing.T) {
"[\"gamma\",\"delta\"]"},
{nil, ""},
} {
result, err := tomlValueStringRepresentation(item.Value, "", false)
result, err := tomlValueStringRepresentation(item.Value, "", "", false)
if err != nil {
t.Errorf("Test %d - unexpected error: %s", idx, err)
}
+4
View File
@@ -222,8 +222,12 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
switch v := value.(type) {
case *Tree:
v.comment = opts.Comment
v.commented = opts.Commented
toInsert = value
case []*Tree:
for i := range v {
v[i].commented = opts.Commented
}
toInsert = value
case *tomlValue:
v.comment = opts.Comment
+27 -21
View File
@@ -28,9 +28,10 @@ type sortNode struct {
// Encodes a string to a TOML-compliant multi-line string value
// This function is a clone of the existing encodeTomlString function, except that whitespace characters
// are preserved. Quotation marks and backslashes are also not escaped.
func encodeMultilineTomlString(value string) string {
func encodeMultilineTomlString(value string, commented string) string {
var b bytes.Buffer
b.WriteString(commented)
for _, rr := range value {
switch rr {
case '\b':
@@ -38,7 +39,7 @@ func encodeMultilineTomlString(value string) string {
case '\t':
b.WriteString("\t")
case '\n':
b.WriteString("\n")
b.WriteString("\n" + commented)
case '\f':
b.WriteString(`\f`)
case '\r':
@@ -91,7 +92,7 @@ func encodeTomlString(value string) string {
return b.String()
}
func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) {
func tomlValueStringRepresentation(v interface{}, commented string, indent string, arraysOneElementPerLine bool) (string, error) {
// this interface check is added to dereference the change made in the writeTo function.
// That change was made to allow this function to see formatting options.
tv, ok := v.(*tomlValue)
@@ -123,12 +124,12 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen
return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil
case string:
if tv.multiline {
return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil
return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil
}
return "\"" + encodeTomlString(value) + "\"", nil
case []byte:
b, _ := v.([]byte)
return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine)
return tomlValueStringRepresentation(string(b), commented, indent, arraysOneElementPerLine)
case bool:
if value {
return "true", nil
@@ -152,7 +153,7 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen
var values []string
for i := 0; i < rv.Len(); i++ {
item := rv.Index(i).Interface()
itemRepr, err := tomlValueStringRepresentation(item, indent, arraysOneElementPerLine)
itemRepr, err := tomlValueStringRepresentation(item, commented, indent, arraysOneElementPerLine)
if err != nil {
return "", err
}
@@ -166,12 +167,12 @@ func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElemen
for _, value := range values {
stringBuffer.WriteString(valueIndent)
stringBuffer.WriteString(value)
stringBuffer.WriteString(commented + value)
stringBuffer.WriteString(`,`)
stringBuffer.WriteString("\n")
}
stringBuffer.WriteString(indent + "]")
stringBuffer.WriteString(indent + commented + "]")
return stringBuffer.String(), nil
}
@@ -270,10 +271,10 @@ func sortAlphabetical(t *Tree) (vals []sortNode) {
}
func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical)
return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, false)
}
func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder) (int64, error) {
func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, parentCommented bool) (int64, error) {
var orderedVals []sortNode
switch ord {
@@ -293,10 +294,6 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
if keyspace != "" {
combinedKey = keyspace + "." + combinedKey
}
var commented string
if t.commented {
commented = "# "
}
switch node := v.(type) {
// node has to be of those two types given how keys are sorted above
@@ -317,24 +314,33 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
return bytesCount, errc
}
}
var commented string
if parentCommented || t.commented || tv.commented {
commented = "# "
}
writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord, parentCommented || t.commented || tv.commented)
if err != nil {
return bytesCount, err
}
case []*Tree:
for _, subTree := range node {
var commented string
if parentCommented || t.commented || subTree.commented {
commented = "# "
}
writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
bytesCount += int64(writtenBytesCount)
if err != nil {
return bytesCount, err
}
bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord, parentCommented || t.commented || subTree.commented)
if err != nil {
return bytesCount, err
}
@@ -347,7 +353,11 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
}
repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
var commented string
if parentCommented || t.commented || v.commented {
commented = "# "
}
repr, err := tomlValueStringRepresentation(v, commented, indent, arraysOneElementPerLine)
if err != nil {
return bytesCount, err
}
@@ -365,10 +375,6 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
}
}
var commented string
if v.commented {
commented = "# "
}
quotedKey := quoteKeyIfNeeded(k)
writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n")
bytesCount += int64(writtenBytesCount)