tomltree_write.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. package toml
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "math"
  7. "math/big"
  8. "reflect"
  9. "sort"
  10. "strconv"
  11. "strings"
  12. "time"
  13. )
  14. type valueComplexity int
  15. const (
  16. valueSimple valueComplexity = iota + 1
  17. valueComplex
  18. )
  19. type sortNode struct {
  20. key string
  21. complexity valueComplexity
  22. }
  23. // Encodes a string to a TOML-compliant multi-line string value
  24. // This function is a clone of the existing encodeTomlString function, except that whitespace characters
  25. // are preserved. Quotation marks and backslashes are also not escaped.
  26. func encodeMultilineTomlString(value string, commented string) string {
  27. var b bytes.Buffer
  28. adjacentQuoteCount := 0
  29. b.WriteString(commented)
  30. for i, rr := range value {
  31. if rr != '"' {
  32. adjacentQuoteCount = 0
  33. } else {
  34. adjacentQuoteCount++
  35. }
  36. switch rr {
  37. case '\b':
  38. b.WriteString(`\b`)
  39. case '\t':
  40. b.WriteString("\t")
  41. case '\n':
  42. b.WriteString("\n" + commented)
  43. case '\f':
  44. b.WriteString(`\f`)
  45. case '\r':
  46. b.WriteString("\r")
  47. case '"':
  48. if adjacentQuoteCount >= 3 || i == len(value)-1 {
  49. adjacentQuoteCount = 0
  50. b.WriteString(`\"`)
  51. } else {
  52. b.WriteString(`"`)
  53. }
  54. case '\\':
  55. b.WriteString(`\`)
  56. default:
  57. intRr := uint16(rr)
  58. if intRr < 0x001F {
  59. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  60. } else {
  61. b.WriteRune(rr)
  62. }
  63. }
  64. }
  65. return b.String()
  66. }
  67. // Encodes a string to a TOML-compliant string value
  68. func encodeTomlString(value string) string {
  69. var b bytes.Buffer
  70. for _, rr := range value {
  71. switch rr {
  72. case '\b':
  73. b.WriteString(`\b`)
  74. case '\t':
  75. b.WriteString(`\t`)
  76. case '\n':
  77. b.WriteString(`\n`)
  78. case '\f':
  79. b.WriteString(`\f`)
  80. case '\r':
  81. b.WriteString(`\r`)
  82. case '"':
  83. b.WriteString(`\"`)
  84. case '\\':
  85. b.WriteString(`\\`)
  86. default:
  87. intRr := uint16(rr)
  88. if intRr < 0x001F {
  89. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  90. } else {
  91. b.WriteRune(rr)
  92. }
  93. }
  94. }
  95. return b.String()
  96. }
  97. func tomlTreeStringRepresentation(t *Tree, ord MarshalOrder) (string, error) {
  98. var orderedVals []sortNode
  99. switch ord {
  100. case OrderPreserve:
  101. orderedVals = sortByLines(t)
  102. default:
  103. orderedVals = sortAlphabetical(t)
  104. }
  105. var values []string
  106. for _, node := range orderedVals {
  107. k := node.key
  108. v := t.values[k]
  109. repr, err := tomlValueStringRepresentation(v, "", "", ord, false)
  110. if err != nil {
  111. return "", err
  112. }
  113. values = append(values, quoteKeyIfNeeded(k)+" = "+repr)
  114. }
  115. return "{ " + strings.Join(values, ", ") + " }", nil
  116. }
  117. func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord MarshalOrder, arraysOneElementPerLine bool) (string, error) {
  118. // this interface check is added to dereference the change made in the writeTo function.
  119. // That change was made to allow this function to see formatting options.
  120. tv, ok := v.(*tomlValue)
  121. if ok {
  122. v = tv.value
  123. } else {
  124. tv = &tomlValue{}
  125. }
  126. switch value := v.(type) {
  127. case uint64:
  128. return strconv.FormatUint(value, 10), nil
  129. case int64:
  130. return strconv.FormatInt(value, 10), nil
  131. case float64:
  132. // Default bit length is full 64
  133. bits := 64
  134. // Float panics if nan is used
  135. if !math.IsNaN(value) {
  136. // if 32 bit accuracy is enough to exactly show, use 32
  137. _, acc := big.NewFloat(value).Float32()
  138. if acc == big.Exact {
  139. bits = 32
  140. }
  141. }
  142. if math.Trunc(value) == value {
  143. return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil
  144. }
  145. return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil
  146. case string:
  147. if tv.multiline {
  148. if tv.literal {
  149. b := strings.Builder{}
  150. b.WriteString("'''\n")
  151. b.Write([]byte(value))
  152. b.WriteString("\n'''")
  153. return b.String(), nil
  154. } else {
  155. return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil
  156. }
  157. }
  158. return "\"" + encodeTomlString(value) + "\"", nil
  159. case []byte:
  160. b, _ := v.([]byte)
  161. return string(b), nil
  162. case bool:
  163. if value {
  164. return "true", nil
  165. }
  166. return "false", nil
  167. case time.Time:
  168. return value.Format(time.RFC3339), nil
  169. case LocalDate:
  170. return value.String(), nil
  171. case LocalDateTime:
  172. return value.String(), nil
  173. case LocalTime:
  174. return value.String(), nil
  175. case *Tree:
  176. return tomlTreeStringRepresentation(value, ord)
  177. case nil:
  178. return "", nil
  179. }
  180. rv := reflect.ValueOf(v)
  181. if rv.Kind() == reflect.Slice {
  182. var values []string
  183. for i := 0; i < rv.Len(); i++ {
  184. item := rv.Index(i).Interface()
  185. itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine)
  186. if err != nil {
  187. return "", err
  188. }
  189. values = append(values, itemRepr)
  190. }
  191. if arraysOneElementPerLine && len(values) > 1 {
  192. stringBuffer := bytes.Buffer{}
  193. valueIndent := indent + ` ` // TODO: move that to a shared encoder state
  194. stringBuffer.WriteString("[\n")
  195. for _, value := range values {
  196. stringBuffer.WriteString(valueIndent)
  197. stringBuffer.WriteString(commented + value)
  198. stringBuffer.WriteString(`,`)
  199. stringBuffer.WriteString("\n")
  200. }
  201. stringBuffer.WriteString(indent + commented + "]")
  202. return stringBuffer.String(), nil
  203. }
  204. return "[" + strings.Join(values, ", ") + "]", nil
  205. }
  206. return "", fmt.Errorf("unsupported value type %T: %v", v, v)
  207. }
  208. func getTreeArrayLine(trees []*Tree) (line int) {
  209. // Prevent returning 0 for empty trees
  210. line = int(^uint(0) >> 1)
  211. // get lowest line number >= 0
  212. for _, tv := range trees {
  213. if tv.position.Line < line || line == 0 {
  214. line = tv.position.Line
  215. }
  216. }
  217. return
  218. }
  219. func sortByLines(t *Tree) (vals []sortNode) {
  220. var (
  221. line int
  222. lines []int
  223. tv *Tree
  224. tom *tomlValue
  225. node sortNode
  226. )
  227. vals = make([]sortNode, 0)
  228. m := make(map[int]sortNode)
  229. for k := range t.values {
  230. v := t.values[k]
  231. switch v.(type) {
  232. case *Tree:
  233. tv = v.(*Tree)
  234. line = tv.position.Line
  235. node = sortNode{key: k, complexity: valueComplex}
  236. case []*Tree:
  237. line = getTreeArrayLine(v.([]*Tree))
  238. node = sortNode{key: k, complexity: valueComplex}
  239. default:
  240. tom = v.(*tomlValue)
  241. line = tom.position.Line
  242. node = sortNode{key: k, complexity: valueSimple}
  243. }
  244. lines = append(lines, line)
  245. vals = append(vals, node)
  246. m[line] = node
  247. }
  248. sort.Ints(lines)
  249. for i, line := range lines {
  250. vals[i] = m[line]
  251. }
  252. return vals
  253. }
  254. func sortAlphabetical(t *Tree) (vals []sortNode) {
  255. var (
  256. node sortNode
  257. simpVals []string
  258. compVals []string
  259. )
  260. vals = make([]sortNode, 0)
  261. m := make(map[string]sortNode)
  262. for k := range t.values {
  263. v := t.values[k]
  264. switch v.(type) {
  265. case *Tree, []*Tree:
  266. node = sortNode{key: k, complexity: valueComplex}
  267. compVals = append(compVals, node.key)
  268. default:
  269. node = sortNode{key: k, complexity: valueSimple}
  270. simpVals = append(simpVals, node.key)
  271. }
  272. vals = append(vals, node)
  273. m[node.key] = node
  274. }
  275. // Simples first to match previous implementation
  276. sort.Strings(simpVals)
  277. i := 0
  278. for _, key := range simpVals {
  279. vals[i] = m[key]
  280. i++
  281. }
  282. sort.Strings(compVals)
  283. for _, key := range compVals {
  284. vals[i] = m[key]
  285. i++
  286. }
  287. return vals
  288. }
  289. func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
  290. return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false, false)
  291. }
  292. func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord MarshalOrder, indentString string, compactComments, parentCommented bool) (int64, error) {
  293. var orderedVals []sortNode
  294. switch ord {
  295. case OrderPreserve:
  296. orderedVals = sortByLines(t)
  297. default:
  298. orderedVals = sortAlphabetical(t)
  299. }
  300. for _, node := range orderedVals {
  301. switch node.complexity {
  302. case valueComplex:
  303. k := node.key
  304. v := t.values[k]
  305. combinedKey := quoteKeyIfNeeded(k)
  306. if keyspace != "" {
  307. combinedKey = keyspace + "." + combinedKey
  308. }
  309. switch node := v.(type) {
  310. // node has to be of those two types given how keys are sorted above
  311. case *Tree:
  312. tv, ok := t.values[k].(*Tree)
  313. if !ok {
  314. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  315. }
  316. if tv.comment != "" {
  317. comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
  318. start := "# "
  319. if strings.HasPrefix(comment, "#") {
  320. start = ""
  321. }
  322. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
  323. bytesCount += int64(writtenBytesCountComment)
  324. if errc != nil {
  325. return bytesCount, errc
  326. }
  327. }
  328. var commented string
  329. if parentCommented || t.commented || tv.commented {
  330. commented = "# "
  331. }
  332. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
  333. bytesCount += int64(writtenBytesCount)
  334. if err != nil {
  335. return bytesCount, err
  336. }
  337. bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || tv.commented)
  338. if err != nil {
  339. return bytesCount, err
  340. }
  341. case []*Tree:
  342. for _, subTree := range node {
  343. var commented string
  344. if parentCommented || t.commented || subTree.commented {
  345. commented = "# "
  346. }
  347. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
  348. bytesCount += int64(writtenBytesCount)
  349. if err != nil {
  350. return bytesCount, err
  351. }
  352. bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || subTree.commented)
  353. if err != nil {
  354. return bytesCount, err
  355. }
  356. }
  357. }
  358. default: // Simple
  359. k := node.key
  360. v, ok := t.values[k].(*tomlValue)
  361. if !ok {
  362. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  363. }
  364. var commented string
  365. if parentCommented || t.commented || v.commented {
  366. commented = "# "
  367. }
  368. repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine)
  369. if err != nil {
  370. return bytesCount, err
  371. }
  372. if v.comment != "" {
  373. comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
  374. start := "# "
  375. if strings.HasPrefix(comment, "#") {
  376. start = ""
  377. }
  378. if !compactComments {
  379. writtenBytesCountComment, errc := writeStrings(w, "\n")
  380. bytesCount += int64(writtenBytesCountComment)
  381. if errc != nil {
  382. return bytesCount, errc
  383. }
  384. }
  385. writtenBytesCountComment, errc := writeStrings(w, indent, start, comment, "\n")
  386. bytesCount += int64(writtenBytesCountComment)
  387. if errc != nil {
  388. return bytesCount, errc
  389. }
  390. }
  391. quotedKey := quoteKeyIfNeeded(k)
  392. writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n")
  393. bytesCount += int64(writtenBytesCount)
  394. if err != nil {
  395. return bytesCount, err
  396. }
  397. }
  398. }
  399. return bytesCount, nil
  400. }
  401. // quote a key if it does not fit the bare key format (A-Za-z0-9_-)
  402. // quoted keys use the same rules as strings
  403. func quoteKeyIfNeeded(k string) string {
  404. // when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain
  405. // keys that have already been quoted.
  406. // not an ideal situation, but good enough of a stop gap.
  407. if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' {
  408. return k
  409. }
  410. isBare := true
  411. for _, r := range k {
  412. if !isValidBareChar(r) {
  413. isBare = false
  414. break
  415. }
  416. }
  417. if isBare {
  418. return k
  419. }
  420. return quoteKey(k)
  421. }
  422. func quoteKey(k string) string {
  423. return "\"" + encodeTomlString(k) + "\""
  424. }
  425. func writeStrings(w io.Writer, s ...string) (int, error) {
  426. var n int
  427. for i := range s {
  428. b, err := io.WriteString(w, s[i])
  429. n += b
  430. if err != nil {
  431. return n, err
  432. }
  433. }
  434. return n, nil
  435. }
  436. // WriteTo encode the Tree as Toml and writes it to the writer w.
  437. // Returns the number of bytes written in case of success, or an error if anything happened.
  438. func (t *Tree) WriteTo(w io.Writer) (int64, error) {
  439. return t.writeTo(w, "", "", 0, false)
  440. }
  441. // ToTomlString generates a human-readable representation of the current tree.
  442. // Output spans multiple lines, and is suitable for ingest by a TOML parser.
  443. // If the conversion cannot be performed, ToString returns a non-nil error.
  444. func (t *Tree) ToTomlString() (string, error) {
  445. b, err := t.Marshal()
  446. if err != nil {
  447. return "", err
  448. }
  449. return string(b), nil
  450. }
  451. // String generates a human-readable representation of the current tree.
  452. // Alias of ToString. Present to implement the fmt.Stringer interface.
  453. func (t *Tree) String() string {
  454. result, _ := t.ToTomlString()
  455. return result
  456. }
  457. // ToMap recursively generates a representation of the tree using Go built-in structures.
  458. // The following types are used:
  459. //
  460. // * bool
  461. // * float64
  462. // * int64
  463. // * string
  464. // * uint64
  465. // * time.Time
  466. // * map[string]interface{} (where interface{} is any of this list)
  467. // * []interface{} (where interface{} is any of this list)
  468. func (t *Tree) ToMap() map[string]interface{} {
  469. result := map[string]interface{}{}
  470. for k, v := range t.values {
  471. switch node := v.(type) {
  472. case []*Tree:
  473. var array []interface{}
  474. for _, item := range node {
  475. array = append(array, item.ToMap())
  476. }
  477. result[k] = array
  478. case *Tree:
  479. result[k] = node.ToMap()
  480. case *tomlValue:
  481. result[k] = tomlValueToGo(node.value)
  482. }
  483. }
  484. return result
  485. }
  486. func tomlValueToGo(v interface{}) interface{} {
  487. if tree, ok := v.(*Tree); ok {
  488. return tree.ToMap()
  489. }
  490. rv := reflect.ValueOf(v)
  491. if rv.Kind() != reflect.Slice {
  492. return v
  493. }
  494. values := make([]interface{}, rv.Len())
  495. for i := 0; i < rv.Len(); i++ {
  496. item := rv.Index(i).Interface()
  497. values[i] = tomlValueToGo(item)
  498. }
  499. return values
  500. }