tomltree_write.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. package toml
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "math"
  7. "reflect"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "time"
  12. )
  13. // Encodes a string to a TOML-compliant multi-line string value
  14. // This function is a clone of the existing encodeTomlString function, except that whitespace characters
  15. // are preserved. Quotation marks and backslashes are also not escaped.
  16. func encodeMultilineTomlString(value string) string {
  17. var b bytes.Buffer
  18. for _, rr := range value {
  19. switch rr {
  20. case '\b':
  21. b.WriteString(`\b`)
  22. case '\t':
  23. b.WriteString("\t")
  24. case '\n':
  25. b.WriteString("\n")
  26. case '\f':
  27. b.WriteString(`\f`)
  28. case '\r':
  29. b.WriteString("\r")
  30. case '"':
  31. b.WriteString(`"`)
  32. case '\\':
  33. b.WriteString(`\`)
  34. default:
  35. intRr := uint16(rr)
  36. if intRr < 0x001F {
  37. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  38. } else {
  39. b.WriteRune(rr)
  40. }
  41. }
  42. }
  43. return b.String()
  44. }
  45. // Encodes a string to a TOML-compliant string value
  46. func encodeTomlString(value string) string {
  47. var b bytes.Buffer
  48. for _, rr := range value {
  49. switch rr {
  50. case '\b':
  51. b.WriteString(`\b`)
  52. case '\t':
  53. b.WriteString(`\t`)
  54. case '\n':
  55. b.WriteString(`\n`)
  56. case '\f':
  57. b.WriteString(`\f`)
  58. case '\r':
  59. b.WriteString(`\r`)
  60. case '"':
  61. b.WriteString(`\"`)
  62. case '\\':
  63. b.WriteString(`\\`)
  64. default:
  65. intRr := uint16(rr)
  66. if intRr < 0x001F {
  67. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  68. } else {
  69. b.WriteRune(rr)
  70. }
  71. }
  72. }
  73. return b.String()
  74. }
  75. func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) {
  76. // this interface check is added to dereference the change made in the writeTo function.
  77. // That change was made to allow this function to see formatting options.
  78. tv, ok := v.(*tomlValue)
  79. if ok {
  80. v = tv.value
  81. } else {
  82. tv = &tomlValue{}
  83. }
  84. switch value := v.(type) {
  85. case uint64:
  86. return strconv.FormatUint(value, 10), nil
  87. case int64:
  88. return strconv.FormatInt(value, 10), nil
  89. case float64:
  90. // Ensure a round float does contain a decimal point. Otherwise feeding
  91. // the output back to the parser would convert to an integer.
  92. if math.Trunc(value) == value {
  93. return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil
  94. }
  95. return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil
  96. case string:
  97. if tv.multiline {
  98. return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil
  99. }
  100. return "\"" + encodeTomlString(value) + "\"", nil
  101. case []byte:
  102. b, _ := v.([]byte)
  103. return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine)
  104. case bool:
  105. if value {
  106. return "true", nil
  107. }
  108. return "false", nil
  109. case time.Time:
  110. return value.Format(time.RFC3339), nil
  111. case nil:
  112. return "", nil
  113. }
  114. rv := reflect.ValueOf(v)
  115. if rv.Kind() == reflect.Slice {
  116. var values []string
  117. for i := 0; i < rv.Len(); i++ {
  118. item := rv.Index(i).Interface()
  119. itemRepr, err := tomlValueStringRepresentation(item, indent, arraysOneElementPerLine)
  120. if err != nil {
  121. return "", err
  122. }
  123. values = append(values, itemRepr)
  124. }
  125. if arraysOneElementPerLine && len(values) > 1 {
  126. stringBuffer := bytes.Buffer{}
  127. valueIndent := indent + ` ` // TODO: move that to a shared encoder state
  128. stringBuffer.WriteString("[\n")
  129. for _, value := range values {
  130. stringBuffer.WriteString(valueIndent)
  131. stringBuffer.WriteString(value)
  132. stringBuffer.WriteString(`,`)
  133. stringBuffer.WriteString("\n")
  134. }
  135. stringBuffer.WriteString(indent + "]")
  136. return stringBuffer.String(), nil
  137. }
  138. return "[" + strings.Join(values, ",") + "]", nil
  139. }
  140. return "", fmt.Errorf("unsupported value type %T: %v", v, v)
  141. }
  142. func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
  143. simpleValuesKeys := make([]string, 0)
  144. complexValuesKeys := make([]string, 0)
  145. for k := range t.values {
  146. v := t.values[k]
  147. switch v.(type) {
  148. case *Tree, []*Tree:
  149. complexValuesKeys = append(complexValuesKeys, k)
  150. default:
  151. simpleValuesKeys = append(simpleValuesKeys, k)
  152. }
  153. }
  154. sort.Strings(simpleValuesKeys)
  155. sort.Strings(complexValuesKeys)
  156. for _, k := range simpleValuesKeys {
  157. v, ok := t.values[k].(*tomlValue)
  158. if !ok {
  159. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  160. }
  161. repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
  162. if err != nil {
  163. return bytesCount, err
  164. }
  165. if v.comment != "" {
  166. comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
  167. start := "# "
  168. if strings.HasPrefix(comment, "#") {
  169. start = ""
  170. }
  171. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
  172. bytesCount += int64(writtenBytesCountComment)
  173. if errc != nil {
  174. return bytesCount, errc
  175. }
  176. }
  177. var commented string
  178. if v.commented {
  179. commented = "# "
  180. }
  181. writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
  182. bytesCount += int64(writtenBytesCount)
  183. if err != nil {
  184. return bytesCount, err
  185. }
  186. }
  187. for _, k := range complexValuesKeys {
  188. v := t.values[k]
  189. combinedKey := k
  190. if keyspace != "" {
  191. combinedKey = keyspace + "." + combinedKey
  192. }
  193. var commented string
  194. if t.commented {
  195. commented = "# "
  196. }
  197. switch node := v.(type) {
  198. // node has to be of those two types given how keys are sorted above
  199. case *Tree:
  200. tv, ok := t.values[k].(*Tree)
  201. if !ok {
  202. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  203. }
  204. if tv.comment != "" {
  205. comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
  206. start := "# "
  207. if strings.HasPrefix(comment, "#") {
  208. start = ""
  209. }
  210. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
  211. bytesCount += int64(writtenBytesCountComment)
  212. if errc != nil {
  213. return bytesCount, errc
  214. }
  215. }
  216. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
  217. bytesCount += int64(writtenBytesCount)
  218. if err != nil {
  219. return bytesCount, err
  220. }
  221. bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine)
  222. if err != nil {
  223. return bytesCount, err
  224. }
  225. case []*Tree:
  226. for _, subTree := range node {
  227. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
  228. bytesCount += int64(writtenBytesCount)
  229. if err != nil {
  230. return bytesCount, err
  231. }
  232. bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine)
  233. if err != nil {
  234. return bytesCount, err
  235. }
  236. }
  237. }
  238. }
  239. return bytesCount, nil
  240. }
  241. func writeStrings(w io.Writer, s ...string) (int, error) {
  242. var n int
  243. for i := range s {
  244. b, err := io.WriteString(w, s[i])
  245. n += b
  246. if err != nil {
  247. return n, err
  248. }
  249. }
  250. return n, nil
  251. }
  252. // WriteTo encode the Tree as Toml and writes it to the writer w.
  253. // Returns the number of bytes written in case of success, or an error if anything happened.
  254. func (t *Tree) WriteTo(w io.Writer) (int64, error) {
  255. return t.writeTo(w, "", "", 0, false)
  256. }
  257. // ToTomlString generates a human-readable representation of the current tree.
  258. // Output spans multiple lines, and is suitable for ingest by a TOML parser.
  259. // If the conversion cannot be performed, ToString returns a non-nil error.
  260. func (t *Tree) ToTomlString() (string, error) {
  261. var buf bytes.Buffer
  262. _, err := t.WriteTo(&buf)
  263. if err != nil {
  264. return "", err
  265. }
  266. return buf.String(), nil
  267. }
  268. // String generates a human-readable representation of the current tree.
  269. // Alias of ToString. Present to implement the fmt.Stringer interface.
  270. func (t *Tree) String() string {
  271. result, _ := t.ToTomlString()
  272. return result
  273. }
  274. // ToMap recursively generates a representation of the tree using Go built-in structures.
  275. // The following types are used:
  276. //
  277. // * bool
  278. // * float64
  279. // * int64
  280. // * string
  281. // * uint64
  282. // * time.Time
  283. // * map[string]interface{} (where interface{} is any of this list)
  284. // * []interface{} (where interface{} is any of this list)
  285. func (t *Tree) ToMap() map[string]interface{} {
  286. result := map[string]interface{}{}
  287. for k, v := range t.values {
  288. switch node := v.(type) {
  289. case []*Tree:
  290. var array []interface{}
  291. for _, item := range node {
  292. array = append(array, item.ToMap())
  293. }
  294. result[k] = array
  295. case *Tree:
  296. result[k] = node.ToMap()
  297. case *tomlValue:
  298. result[k] = node.value
  299. }
  300. }
  301. return result
  302. }