From 4db24e88198d9c0b062e5b3be1165d2757258699 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 23 Nov 2016 16:14:33 +0100 Subject: [PATCH] Make values come before tables in ToString output If no order on the key is enforced in ToString, the following tree: foo = 1 bar = "baz" foobar = true [qux] foo = 1 bar = "baz" may come out as: bar = "baz" foobar = true [qux] foo = 1 bar = "baz" foo = 1 which is incorrect, since putting that back to the parser would panic because of a duplicated key (qux.foo). Those changes make sure that leaf values come before tables in the ToString output. --- tomltree_conversions.go | 37 ++++++++++++++++++++++-------------- tomltree_conversions_test.go | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/tomltree_conversions.go b/tomltree_conversions.go index bf9321b3..08efebcc 100644 --- a/tomltree_conversions.go +++ b/tomltree_conversions.go @@ -94,55 +94,64 @@ func toTomlValue(item interface{}, indent int) string { // Recursive support function for ToString() // Outputs a tree, using the provided keyspace to prefix group names func (t *TomlTree) toToml(indent, keyspace string) string { - result := "" + resultChunks := []string{} for k, v := range t.values { // figure out the keyspace combinedKey := k if keyspace != "" { combinedKey = keyspace + "." + combinedKey } + resultChunk := "" // output based on type switch node := v.(type) { case []*TomlTree: for _, item := range node { if len(item.Keys()) > 0 { - result += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey) + resultChunk += fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey) } - result += item.toToml(indent+" ", combinedKey) + resultChunk += item.toToml(indent+" ", combinedKey) } + resultChunks = append(resultChunks, resultChunk) case *TomlTree: if len(node.Keys()) > 0 { - result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) + resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) } - result += node.toToml(indent+" ", combinedKey) + resultChunk += node.toToml(indent+" ", combinedKey) + resultChunks = append(resultChunks, resultChunk) case map[string]interface{}: sub := TreeFromMap(node) if len(sub.Keys()) > 0 { - result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) + resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) } - result += sub.toToml(indent+" ", combinedKey) + resultChunk += sub.toToml(indent+" ", combinedKey) + resultChunks = append(resultChunks, resultChunk) case map[string]string: sub := TreeFromMap(convertMapStringString(node)) if len(sub.Keys()) > 0 { - result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) + resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) } - result += sub.toToml(indent+" ", combinedKey) + resultChunk += sub.toToml(indent+" ", combinedKey) + resultChunks = append(resultChunks, resultChunk) case map[interface{}]interface{}: sub := TreeFromMap(convertMapInterfaceInterface(node)) if len(sub.Keys()) > 0 { - result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) + resultChunk += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) } - result += sub.toToml(indent+" ", combinedKey) + resultChunk += sub.toToml(indent+" ", combinedKey) + resultChunks = append(resultChunks, resultChunk) case *tomlValue: - result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0)) + resultChunk = fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0)) + resultChunks = append([]string{resultChunk}, resultChunks...) default: - result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0)) + resultChunk = fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0)) + resultChunks = append([]string{resultChunk}, resultChunks...) } + } - return result + return strings.Join(resultChunks, "") } func convertMapStringString(in map[string]string) map[string]interface{} { diff --git a/tomltree_conversions_test.go b/tomltree_conversions_test.go index 58dbf4e0..4ad90097 100644 --- a/tomltree_conversions_test.go +++ b/tomltree_conversions_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" "time" + "strings" ) func TestTomlTreeConversionToString(t *testing.T) { @@ -28,6 +29,41 @@ points = { x = 1, y = 2 }`) }) } +func TestTomlTreeConversionToStringKeysOrders(t *testing.T) { + for i := 0; i < 100; i++ { + tree, _ := Load(` + foobar = true + bar = "baz" + foo = 1 + [qux] + foo = 1 + bar = "baz2"`) + + stringRepr := tree.ToString() + + t.Log("Intermediate string representation:") + t.Log(stringRepr) + + r := strings.NewReader(stringRepr) + toml, err := LoadReader(r) + + + if err != nil { + t.Fatal("Unexpected error:", err) + } + + assertTree(t, toml, err, map[string]interface{}{ + "foobar": true, + "bar": "baz", + "foo": 1, + "qux": map[string]interface{}{ + "foo": 1, + "bar": "baz2", + }, + }) + } +} + func testMaps(t *testing.T, actual, expected map[string]interface{}) { if !reflect.DeepEqual(actual, expected) { t.Fatal("trees aren't equal.\n", "Expected:\n", expected, "\nActual:\n", actual)