TomlTree.ToMap (#59) * Extract TomlTree conversion to its own file * Implement ToMap * Reorder imports in tomltree_conversions
diff --git a/toml.go b/toml.go index f3de359..3975d01 100644 --- a/toml.go +++ b/toml.go
@@ -6,9 +6,7 @@ "io" "os" "runtime" - "strconv" "strings" - "time" ) type tomlValue struct { @@ -248,106 +246,6 @@ 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. func (t *TomlTree) Query(query string) (*QueryResult, error) { q, err := CompileQuery(query) @@ -357,12 +255,6 @@ 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. func LoadReader(reader io.Reader) (tree *TomlTree, err error) { defer func() {
diff --git a/tomltree_conversions.go b/tomltree_conversions.go new file mode 100644 index 0000000..caa2b4a --- /dev/null +++ b/tomltree_conversions.go
@@ -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 +}
diff --git a/tomltree_conversions_test.go b/tomltree_conversions_test.go new file mode 100644 index 0000000..a420c12 --- /dev/null +++ b/tomltree_conversions_test.go
@@ -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) +}