TomlTree.ToMap (#59)
* Extract TomlTree conversion to its own file * Implement ToMap * Reorder imports in tomltree_conversions
This commit is contained in:
@@ -6,9 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tomlValue struct {
|
type tomlValue struct {
|
||||||
@@ -248,106 +246,6 @@ func (t *TomlTree) createSubTree(keys []string, pos Position) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodes a string to a TOML-compliant string value
|
|
||||||
func encodeTomlString(value string) string {
|
|
||||||
result := ""
|
|
||||||
for _, rr := range value {
|
|
||||||
intRr := uint16(rr)
|
|
||||||
switch rr {
|
|
||||||
case '\b':
|
|
||||||
result += "\\b"
|
|
||||||
case '\t':
|
|
||||||
result += "\\t"
|
|
||||||
case '\n':
|
|
||||||
result += "\\n"
|
|
||||||
case '\f':
|
|
||||||
result += "\\f"
|
|
||||||
case '\r':
|
|
||||||
result += "\\r"
|
|
||||||
case '"':
|
|
||||||
result += "\\\""
|
|
||||||
case '\\':
|
|
||||||
result += "\\\\"
|
|
||||||
default:
|
|
||||||
if intRr < 0x001F {
|
|
||||||
result += fmt.Sprintf("\\u%0.4X", intRr)
|
|
||||||
} else {
|
|
||||||
result += string(rr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value print support function for ToString()
|
|
||||||
// Outputs the TOML compliant string representation of a value
|
|
||||||
func toTomlValue(item interface{}, indent int) string {
|
|
||||||
tab := strings.Repeat(" ", indent)
|
|
||||||
switch value := item.(type) {
|
|
||||||
case int64:
|
|
||||||
return tab + strconv.FormatInt(value, 10)
|
|
||||||
case float64:
|
|
||||||
return tab + strconv.FormatFloat(value, 'f', -1, 64)
|
|
||||||
case string:
|
|
||||||
return tab + "\"" + encodeTomlString(value) + "\""
|
|
||||||
case bool:
|
|
||||||
if value {
|
|
||||||
return "true"
|
|
||||||
}
|
|
||||||
return "false"
|
|
||||||
case time.Time:
|
|
||||||
return tab + value.Format(time.RFC3339)
|
|
||||||
case []interface{}:
|
|
||||||
result := tab + "[\n"
|
|
||||||
for _, item := range value {
|
|
||||||
result += toTomlValue(item, indent+2) + ",\n"
|
|
||||||
}
|
|
||||||
return result + tab + "]"
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unsupported value type: %v", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 := ""
|
|
||||||
for k, v := range t.values {
|
|
||||||
// figure out the keyspace
|
|
||||||
combinedKey := k
|
|
||||||
if keyspace != "" {
|
|
||||||
combinedKey = keyspace + "." + combinedKey
|
|
||||||
}
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
result += item.toToml(indent+" ", combinedKey)
|
|
||||||
}
|
|
||||||
case *TomlTree:
|
|
||||||
if len(node.Keys()) > 0 {
|
|
||||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
|
||||||
}
|
|
||||||
result += node.toToml(indent+" ", combinedKey)
|
|
||||||
case map[string]interface{}:
|
|
||||||
sub := TreeFromMap(node)
|
|
||||||
|
|
||||||
if len(sub.Keys()) > 0 {
|
|
||||||
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
|
||||||
}
|
|
||||||
result += sub.toToml(indent+" ", combinedKey)
|
|
||||||
case *tomlValue:
|
|
||||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
|
|
||||||
default:
|
|
||||||
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query compiles and executes a query on a tree and returns the query result.
|
// Query compiles and executes a query on a tree and returns the query result.
|
||||||
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
||||||
q, err := CompileQuery(query)
|
q, err := CompileQuery(query)
|
||||||
@@ -357,12 +255,6 @@ func (t *TomlTree) Query(query string) (*QueryResult, error) {
|
|||||||
return q.Execute(t), nil
|
return q.Execute(t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToString generates a human-readable representation of the current tree.
|
|
||||||
// Output spans multiple lines, and is suitable for ingest by a TOML parser
|
|
||||||
func (t *TomlTree) ToString() string {
|
|
||||||
return t.toToml("", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadReader creates a TomlTree from any io.Reader.
|
// LoadReader creates a TomlTree from any io.Reader.
|
||||||
func LoadReader(reader io.Reader) (tree *TomlTree, err error) {
|
func LoadReader(reader io.Reader) (tree *TomlTree, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
// Tools to convert a TomlTree to different representations
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// encodes a string to a TOML-compliant string value
|
||||||
|
func encodeTomlString(value string) string {
|
||||||
|
result := ""
|
||||||
|
for _, rr := range value {
|
||||||
|
intRr := uint16(rr)
|
||||||
|
switch rr {
|
||||||
|
case '\b':
|
||||||
|
result += "\\b"
|
||||||
|
case '\t':
|
||||||
|
result += "\\t"
|
||||||
|
case '\n':
|
||||||
|
result += "\\n"
|
||||||
|
case '\f':
|
||||||
|
result += "\\f"
|
||||||
|
case '\r':
|
||||||
|
result += "\\r"
|
||||||
|
case '"':
|
||||||
|
result += "\\\""
|
||||||
|
case '\\':
|
||||||
|
result += "\\\\"
|
||||||
|
default:
|
||||||
|
if intRr < 0x001F {
|
||||||
|
result += fmt.Sprintf("\\u%0.4X", intRr)
|
||||||
|
} else {
|
||||||
|
result += string(rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value print support function for ToString()
|
||||||
|
// Outputs the TOML compliant string representation of a value
|
||||||
|
func toTomlValue(item interface{}, indent int) string {
|
||||||
|
tab := strings.Repeat(" ", indent)
|
||||||
|
switch value := item.(type) {
|
||||||
|
case int64:
|
||||||
|
return tab + strconv.FormatInt(value, 10)
|
||||||
|
case float64:
|
||||||
|
return tab + strconv.FormatFloat(value, 'f', -1, 64)
|
||||||
|
case string:
|
||||||
|
return tab + "\"" + encodeTomlString(value) + "\""
|
||||||
|
case bool:
|
||||||
|
if value {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
case time.Time:
|
||||||
|
return tab + value.Format(time.RFC3339)
|
||||||
|
case []interface{}:
|
||||||
|
result := tab + "[\n"
|
||||||
|
for _, item := range value {
|
||||||
|
result += toTomlValue(item, indent+2) + ",\n"
|
||||||
|
}
|
||||||
|
return result + tab + "]"
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported value type: %v", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := ""
|
||||||
|
for k, v := range t.values {
|
||||||
|
// figure out the keyspace
|
||||||
|
combinedKey := k
|
||||||
|
if keyspace != "" {
|
||||||
|
combinedKey = keyspace + "." + combinedKey
|
||||||
|
}
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
result += item.toToml(indent+" ", combinedKey)
|
||||||
|
}
|
||||||
|
case *TomlTree:
|
||||||
|
if len(node.Keys()) > 0 {
|
||||||
|
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||||
|
}
|
||||||
|
result += node.toToml(indent+" ", combinedKey)
|
||||||
|
case map[string]interface{}:
|
||||||
|
sub := TreeFromMap(node)
|
||||||
|
|
||||||
|
if len(sub.Keys()) > 0 {
|
||||||
|
result += fmt.Sprintf("\n%s[%s]\n", indent, combinedKey)
|
||||||
|
}
|
||||||
|
result += sub.toToml(indent+" ", combinedKey)
|
||||||
|
case *tomlValue:
|
||||||
|
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(node.value, 0))
|
||||||
|
default:
|
||||||
|
result += fmt.Sprintf("%s%s = %s\n", indent, k, toTomlValue(v, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString generates a human-readable representation of the current tree.
|
||||||
|
// Output spans multiple lines, and is suitable for ingest by a TOML parser
|
||||||
|
func (t *TomlTree) ToString() string {
|
||||||
|
return t.toToml("", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToMap recursively generates a representation of the current tree using map[string]interface{}.
|
||||||
|
func (t *TomlTree) ToMap() map[string]interface{} {
|
||||||
|
result := map[string]interface{}{}
|
||||||
|
|
||||||
|
for k, v := range t.values {
|
||||||
|
switch node := v.(type) {
|
||||||
|
case []*TomlTree:
|
||||||
|
result[k] = make([]interface{}, 0)
|
||||||
|
for _, item := range node {
|
||||||
|
result[k] = item.ToMap()
|
||||||
|
}
|
||||||
|
case *TomlTree:
|
||||||
|
result[k] = node.ToMap()
|
||||||
|
case map[string]interface{}:
|
||||||
|
sub := TreeFromMap(node)
|
||||||
|
result[k] = sub.ToMap()
|
||||||
|
case *tomlValue:
|
||||||
|
result[k] = node.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlTreeConversionToMapSimple(t *testing.T) {
|
||||||
|
tree, _ := Load("a = 42\nb = 17")
|
||||||
|
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"a": int64(42),
|
||||||
|
"b": int64(17),
|
||||||
|
}
|
||||||
|
|
||||||
|
testMaps(t, tree.ToMap(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTomlTreeConversionToMapExampleFile(t *testing.T) {
|
||||||
|
tree, _ := LoadFile("example.toml")
|
||||||
|
expected := map[string]interface{}{
|
||||||
|
"title": "TOML Example",
|
||||||
|
"owner": map[string]interface{}{
|
||||||
|
"name": "Tom Preston-Werner",
|
||||||
|
"organization": "GitHub",
|
||||||
|
"bio": "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
||||||
|
"dob": time.Date(1979, time.May, 27, 7, 32, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
"database": map[string]interface{}{
|
||||||
|
"server": "192.168.1.1",
|
||||||
|
"ports": []interface{}{int64(8001), int64(8001), int64(8002)},
|
||||||
|
"connection_max": int64(5000),
|
||||||
|
"enabled": true,
|
||||||
|
},
|
||||||
|
"servers": map[string]interface{}{
|
||||||
|
"alpha": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.1",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
"beta": map[string]interface{}{
|
||||||
|
"ip": "10.0.0.2",
|
||||||
|
"dc": "eqdc10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"clients": map[string]interface{}{
|
||||||
|
"data": []interface{}{
|
||||||
|
[]interface{}{"gamma", "delta"},
|
||||||
|
[]interface{}{int64(1), int64(2)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testMaps(t, tree.ToMap(), expected)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user