vendor/github.com/pelletier/go-toml/tomltree_write.go
changeset 251 1c52a0eeb952
parent 242 2a9ec03fe5a1
child 256 6d9efbef00a9
--- a/vendor/github.com/pelletier/go-toml/tomltree_write.go	Wed Sep 18 19:17:42 2019 +0200
+++ b/vendor/github.com/pelletier/go-toml/tomltree_write.go	Sun Feb 16 18:54:01 2020 +0100
@@ -5,6 +5,7 @@
 	"fmt"
 	"io"
 	"math"
+	"math/big"
 	"reflect"
 	"sort"
 	"strconv"
@@ -12,6 +13,18 @@
 	"time"
 )
 
+type valueComplexity int
+
+const (
+	valueSimple valueComplexity = iota + 1
+	valueComplex
+)
+
+type sortNode struct {
+	key        string
+	complexity valueComplexity
+}
+
 // 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.
@@ -94,12 +107,20 @@
 	case int64:
 		return strconv.FormatInt(value, 10), nil
 	case float64:
-		// Ensure a round float does contain a decimal point. Otherwise feeding
-		// the output back to the parser would convert to an integer.
+		// Default bit length is full 64
+		bits := 64
+		// Float panics if nan is used
+		if !math.IsNaN(value) {
+			// if 32 bit accuracy is enough to exactly show, use 32
+			_, acc := big.NewFloat(value).Float32()
+			if acc == big.Exact {
+				bits = 32
+			}
+		}
 		if math.Trunc(value) == value {
-			return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil
+			return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil
 		}
-		return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil
+		return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil
 	case string:
 		if tv.multiline {
 			return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil
@@ -115,6 +136,12 @@
 		return "false", nil
 	case time.Time:
 		return value.Format(time.RFC3339), nil
+	case LocalDate:
+		return value.String(), nil
+	case LocalDateTime:
+		return value.String(), nil
+	case LocalTime:
+		return value.String(), nil
 	case nil:
 		return "", nil
 	}
@@ -153,117 +180,233 @@
 	return "", fmt.Errorf("unsupported value type %T: %v", v, v)
 }
 
-func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
-	simpleValuesKeys := make([]string, 0)
-	complexValuesKeys := make([]string, 0)
+func getTreeArrayLine(trees []*Tree) (line int) {
+	// get lowest line number that is not 0
+	for _, tv := range trees {
+		if tv.position.Line < line || line == 0 {
+			line = tv.position.Line
+		}
+	}
+	return
+}
+
+func sortByLines(t *Tree) (vals []sortNode) {
+	var (
+		line  int
+		lines []int
+		tv    *Tree
+		tom   *tomlValue
+		node  sortNode
+	)
+	vals = make([]sortNode, 0)
+	m := make(map[int]sortNode)
+
+	for k := range t.values {
+		v := t.values[k]
+		switch v.(type) {
+		case *Tree:
+			tv = v.(*Tree)
+			line = tv.position.Line
+			node = sortNode{key: k, complexity: valueComplex}
+		case []*Tree:
+			line = getTreeArrayLine(v.([]*Tree))
+			node = sortNode{key: k, complexity: valueComplex}
+		default:
+			tom = v.(*tomlValue)
+			line = tom.position.Line
+			node = sortNode{key: k, complexity: valueSimple}
+		}
+		lines = append(lines, line)
+		vals = append(vals, node)
+		m[line] = node
+	}
+	sort.Ints(lines)
+
+	for i, line := range lines {
+		vals[i] = m[line]
+	}
+
+	return vals
+}
+
+func sortAlphabetical(t *Tree) (vals []sortNode) {
+	var (
+		node     sortNode
+		simpVals []string
+		compVals []string
+	)
+	vals = make([]sortNode, 0)
+	m := make(map[string]sortNode)
 
 	for k := range t.values {
 		v := t.values[k]
 		switch v.(type) {
 		case *Tree, []*Tree:
-			complexValuesKeys = append(complexValuesKeys, k)
+			node = sortNode{key: k, complexity: valueComplex}
+			compVals = append(compVals, node.key)
 		default:
-			simpleValuesKeys = append(simpleValuesKeys, k)
+			node = sortNode{key: k, complexity: valueSimple}
+			simpVals = append(simpVals, node.key)
 		}
+		vals = append(vals, node)
+		m[node.key] = node
+	}
+
+	// Simples first to match previous implementation
+	sort.Strings(simpVals)
+	i := 0
+	for _, key := range simpVals {
+		vals[i] = m[key]
+		i++
+	}
+
+	sort.Strings(compVals)
+	for _, key := range compVals {
+		vals[i] = m[key]
+		i++
+	}
+
+	return vals
+}
+
+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)
+}
+
+func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder) (int64, error) {
+	var orderedVals []sortNode
+
+	switch ord {
+	case OrderPreserve:
+		orderedVals = sortByLines(t)
+	default:
+		orderedVals = sortAlphabetical(t)
 	}
 
-	sort.Strings(simpleValuesKeys)
-	sort.Strings(complexValuesKeys)
-
-	for _, k := range simpleValuesKeys {
-		v, ok := t.values[k].(*tomlValue)
-		if !ok {
-			return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
-		}
+	for _, node := range orderedVals {
+		switch node.complexity {
+		case valueComplex:
+			k := node.key
+			v := t.values[k]
 
-		repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
-		if err != nil {
-			return bytesCount, err
-		}
-
-		if v.comment != "" {
-			comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
-			start := "# "
-			if strings.HasPrefix(comment, "#") {
-				start = ""
+			combinedKey := k
+			if keyspace != "" {
+				combinedKey = keyspace + "." + combinedKey
 			}
-			writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
-			bytesCount += int64(writtenBytesCountComment)
-			if errc != nil {
-				return bytesCount, errc
+			var commented string
+			if t.commented {
+				commented = "# "
 			}
-		}
 
-		var commented string
-		if v.commented {
-			commented = "# "
-		}
-		writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
-		bytesCount += int64(writtenBytesCount)
-		if err != nil {
-			return bytesCount, err
-		}
-	}
-
-	for _, k := range complexValuesKeys {
-		v := t.values[k]
+			switch node := v.(type) {
+			// node has to be of those two types given how keys are sorted above
+			case *Tree:
+				tv, ok := t.values[k].(*Tree)
+				if !ok {
+					return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
+				}
+				if tv.comment != "" {
+					comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
+					start := "# "
+					if strings.HasPrefix(comment, "#") {
+						start = ""
+					}
+					writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
+					bytesCount += int64(writtenBytesCountComment)
+					if errc != nil {
+						return bytesCount, errc
+					}
+				}
+				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)
+				if err != nil {
+					return bytesCount, err
+				}
+			case []*Tree:
+				for _, subTree := range node {
+					writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
+					bytesCount += int64(writtenBytesCount)
+					if err != nil {
+						return bytesCount, err
+					}
 
-		combinedKey := k
-		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
-		case *Tree:
-			tv, ok := t.values[k].(*Tree)
+					bytesCount, err = subTree.writeToOrdered(w, indent+"  ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
+					if err != nil {
+						return bytesCount, err
+					}
+				}
+			}
+		default: // Simple
+			k := node.key
+			v, ok := t.values[k].(*tomlValue)
 			if !ok {
 				return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
 			}
-			if tv.comment != "" {
-				comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
+
+			repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
+			if err != nil {
+				return bytesCount, err
+			}
+
+			if v.comment != "" {
+				comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
 				start := "# "
 				if strings.HasPrefix(comment, "#") {
 					start = ""
 				}
-				writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
+				writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
 				bytesCount += int64(writtenBytesCountComment)
 				if errc != nil {
 					return bytesCount, errc
 				}
 			}
-			writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
+
+			var commented string
+			if v.commented {
+				commented = "# "
+			}
+			quotedKey := quoteKeyIfNeeded(k)
+			writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n")
 			bytesCount += int64(writtenBytesCount)
 			if err != nil {
 				return bytesCount, err
 			}
-			bytesCount, err = node.writeTo(w, indent+"  ", combinedKey, bytesCount, arraysOneElementPerLine)
-			if err != nil {
-				return bytesCount, err
-			}
-		case []*Tree:
-			for _, subTree := range node {
-				writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
-				bytesCount += int64(writtenBytesCount)
-				if err != nil {
-					return bytesCount, err
-				}
-
-				bytesCount, err = subTree.writeTo(w, indent+"  ", combinedKey, bytesCount, arraysOneElementPerLine)
-				if err != nil {
-					return bytesCount, err
-				}
-			}
 		}
 	}
 
 	return bytesCount, nil
 }
 
+// quote a key if it does not fit the bare key format (A-Za-z0-9_-)
+// quoted keys use the same rules as strings
+func quoteKeyIfNeeded(k string) string {
+	// when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain
+	// keys that have already been quoted.
+	// not an ideal situation, but good enough of a stop gap.
+	if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' {
+		return k
+	}
+	isBare := true
+	for _, r := range k {
+		if !isValidBareChar(r) {
+			isBare = false
+			break
+		}
+	}
+	if isBare {
+		return k
+	}
+	return quoteKey(k)
+}
+
+func quoteKey(k string) string {
+	return "\"" + encodeTomlString(k) + "\""
+}
+
 func writeStrings(w io.Writer, s ...string) (int, error) {
 	var n int
 	for i := range s {
@@ -286,12 +429,11 @@
 // Output spans multiple lines, and is suitable for ingest by a TOML parser.
 // If the conversion cannot be performed, ToString returns a non-nil error.
 func (t *Tree) ToTomlString() (string, error) {
-	var buf bytes.Buffer
-	_, err := t.WriteTo(&buf)
+	b, err := t.Marshal()
 	if err != nil {
 		return "", err
 	}
-	return buf.String(), nil
+	return string(b), nil
 }
 
 // String generates a human-readable representation of the current tree.