You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tomltree_write.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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. return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil
  149. }
  150. return "\"" + encodeTomlString(value) + "\"", nil
  151. case []byte:
  152. b, _ := v.([]byte)
  153. return string(b), nil
  154. case bool:
  155. if value {
  156. return "true", nil
  157. }
  158. return "false", nil
  159. case time.Time:
  160. return value.Format(time.RFC3339), nil
  161. case LocalDate:
  162. return value.String(), nil
  163. case LocalDateTime:
  164. return value.String(), nil
  165. case LocalTime:
  166. return value.String(), nil
  167. case *Tree:
  168. return tomlTreeStringRepresentation(value, ord)
  169. case nil:
  170. return "", nil
  171. }
  172. rv := reflect.ValueOf(v)
  173. if rv.Kind() == reflect.Slice {
  174. var values []string
  175. for i := 0; i < rv.Len(); i++ {
  176. item := rv.Index(i).Interface()
  177. itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine)
  178. if err != nil {
  179. return "", err
  180. }
  181. values = append(values, itemRepr)
  182. }
  183. if arraysOneElementPerLine && len(values) > 1 {
  184. stringBuffer := bytes.Buffer{}
  185. valueIndent := indent + ` ` // TODO: move that to a shared encoder state
  186. stringBuffer.WriteString("[\n")
  187. for _, value := range values {
  188. stringBuffer.WriteString(valueIndent)
  189. stringBuffer.WriteString(commented + value)
  190. stringBuffer.WriteString(`,`)
  191. stringBuffer.WriteString("\n")
  192. }
  193. stringBuffer.WriteString(indent + commented + "]")
  194. return stringBuffer.String(), nil
  195. }
  196. return "[" + strings.Join(values, ", ") + "]", nil
  197. }
  198. return "", fmt.Errorf("unsupported value type %T: %v", v, v)
  199. }
  200. func getTreeArrayLine(trees []*Tree) (line int) {
  201. // get lowest line number that is not 0
  202. for _, tv := range trees {
  203. if tv.position.Line < line || line == 0 {
  204. line = tv.position.Line
  205. }
  206. }
  207. return
  208. }
  209. func sortByLines(t *Tree) (vals []sortNode) {
  210. var (
  211. line int
  212. lines []int
  213. tv *Tree
  214. tom *tomlValue
  215. node sortNode
  216. )
  217. vals = make([]sortNode, 0)
  218. m := make(map[int]sortNode)
  219. for k := range t.values {
  220. v := t.values[k]
  221. switch v.(type) {
  222. case *Tree:
  223. tv = v.(*Tree)
  224. line = tv.position.Line
  225. node = sortNode{key: k, complexity: valueComplex}
  226. case []*Tree:
  227. line = getTreeArrayLine(v.([]*Tree))
  228. node = sortNode{key: k, complexity: valueComplex}
  229. default:
  230. tom = v.(*tomlValue)
  231. line = tom.position.Line
  232. node = sortNode{key: k, complexity: valueSimple}
  233. }
  234. lines = append(lines, line)
  235. vals = append(vals, node)
  236. m[line] = node
  237. }
  238. sort.Ints(lines)
  239. for i, line := range lines {
  240. vals[i] = m[line]
  241. }
  242. return vals
  243. }
  244. func sortAlphabetical(t *Tree) (vals []sortNode) {
  245. var (
  246. node sortNode
  247. simpVals []string
  248. compVals []string
  249. )
  250. vals = make([]sortNode, 0)
  251. m := make(map[string]sortNode)
  252. for k := range t.values {
  253. v := t.values[k]
  254. switch v.(type) {
  255. case *Tree, []*Tree:
  256. node = sortNode{key: k, complexity: valueComplex}
  257. compVals = append(compVals, node.key)
  258. default:
  259. node = sortNode{key: k, complexity: valueSimple}
  260. simpVals = append(simpVals, node.key)
  261. }
  262. vals = append(vals, node)
  263. m[node.key] = node
  264. }
  265. // Simples first to match previous implementation
  266. sort.Strings(simpVals)
  267. i := 0
  268. for _, key := range simpVals {
  269. vals[i] = m[key]
  270. i++
  271. }
  272. sort.Strings(compVals)
  273. for _, key := range compVals {
  274. vals[i] = m[key]
  275. i++
  276. }
  277. return vals
  278. }
  279. func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
  280. return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false)
  281. }
  282. func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, indentString string, parentCommented bool) (int64, error) {
  283. var orderedVals []sortNode
  284. switch ord {
  285. case OrderPreserve:
  286. orderedVals = sortByLines(t)
  287. default:
  288. orderedVals = sortAlphabetical(t)
  289. }
  290. for _, node := range orderedVals {
  291. switch node.complexity {
  292. case valueComplex:
  293. k := node.key
  294. v := t.values[k]
  295. combinedKey := quoteKeyIfNeeded(k)
  296. if keyspace != "" {
  297. combinedKey = keyspace + "." + combinedKey
  298. }
  299. switch node := v.(type) {
  300. // node has to be of those two types given how keys are sorted above
  301. case *Tree:
  302. tv, ok := t.values[k].(*Tree)
  303. if !ok {
  304. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  305. }
  306. if tv.comment != "" {
  307. comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
  308. start := "# "
  309. if strings.HasPrefix(comment, "#") {
  310. start = ""
  311. }
  312. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
  313. bytesCount += int64(writtenBytesCountComment)
  314. if errc != nil {
  315. return bytesCount, errc
  316. }
  317. }
  318. var commented string
  319. if parentCommented || t.commented || tv.commented {
  320. commented = "# "
  321. }
  322. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
  323. bytesCount += int64(writtenBytesCount)
  324. if err != nil {
  325. return bytesCount, err
  326. }
  327. bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || tv.commented)
  328. if err != nil {
  329. return bytesCount, err
  330. }
  331. case []*Tree:
  332. for _, subTree := range node {
  333. var commented string
  334. if parentCommented || t.commented || subTree.commented {
  335. commented = "# "
  336. }
  337. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
  338. bytesCount += int64(writtenBytesCount)
  339. if err != nil {
  340. return bytesCount, err
  341. }
  342. bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || subTree.commented)
  343. if err != nil {
  344. return bytesCount, err
  345. }
  346. }
  347. }
  348. default: // Simple
  349. k := node.key
  350. v, ok := t.values[k].(*tomlValue)
  351. if !ok {
  352. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  353. }
  354. var commented string
  355. if parentCommented || t.commented || v.commented {
  356. commented = "# "
  357. }
  358. repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine)
  359. if err != nil {
  360. return bytesCount, err
  361. }
  362. if v.comment != "" {
  363. comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
  364. start := "# "
  365. if strings.HasPrefix(comment, "#") {
  366. start = ""
  367. }
  368. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
  369. bytesCount += int64(writtenBytesCountComment)
  370. if errc != nil {
  371. return bytesCount, errc
  372. }
  373. }
  374. quotedKey := quoteKeyIfNeeded(k)
  375. writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n")
  376. bytesCount += int64(writtenBytesCount)
  377. if err != nil {
  378. return bytesCount, err
  379. }
  380. }
  381. }
  382. return bytesCount, nil
  383. }
  384. // quote a key if it does not fit the bare key format (A-Za-z0-9_-)
  385. // quoted keys use the same rules as strings
  386. func quoteKeyIfNeeded(k string) string {
  387. // when encoding a map with the 'quoteMapKeys' option enabled, the tree will contain
  388. // keys that have already been quoted.
  389. // not an ideal situation, but good enough of a stop gap.
  390. if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' {
  391. return k
  392. }
  393. isBare := true
  394. for _, r := range k {
  395. if !isValidBareChar(r) {
  396. isBare = false
  397. break
  398. }
  399. }
  400. if isBare {
  401. return k
  402. }
  403. return quoteKey(k)
  404. }
  405. func quoteKey(k string) string {
  406. return "\"" + encodeTomlString(k) + "\""
  407. }
  408. func writeStrings(w io.Writer, s ...string) (int, error) {
  409. var n int
  410. for i := range s {
  411. b, err := io.WriteString(w, s[i])
  412. n += b
  413. if err != nil {
  414. return n, err
  415. }
  416. }
  417. return n, nil
  418. }
  419. // WriteTo encode the Tree as Toml and writes it to the writer w.
  420. // Returns the number of bytes written in case of success, or an error if anything happened.
  421. func (t *Tree) WriteTo(w io.Writer) (int64, error) {
  422. return t.writeTo(w, "", "", 0, false)
  423. }
  424. // ToTomlString generates a human-readable representation of the current tree.
  425. // Output spans multiple lines, and is suitable for ingest by a TOML parser.
  426. // If the conversion cannot be performed, ToString returns a non-nil error.
  427. func (t *Tree) ToTomlString() (string, error) {
  428. b, err := t.Marshal()
  429. if err != nil {
  430. return "", err
  431. }
  432. return string(b), nil
  433. }
  434. // String generates a human-readable representation of the current tree.
  435. // Alias of ToString. Present to implement the fmt.Stringer interface.
  436. func (t *Tree) String() string {
  437. result, _ := t.ToTomlString()
  438. return result
  439. }
  440. // ToMap recursively generates a representation of the tree using Go built-in structures.
  441. // The following types are used:
  442. //
  443. // * bool
  444. // * float64
  445. // * int64
  446. // * string
  447. // * uint64
  448. // * time.Time
  449. // * map[string]interface{} (where interface{} is any of this list)
  450. // * []interface{} (where interface{} is any of this list)
  451. func (t *Tree) ToMap() map[string]interface{} {
  452. result := map[string]interface{}{}
  453. for k, v := range t.values {
  454. switch node := v.(type) {
  455. case []*Tree:
  456. var array []interface{}
  457. for _, item := range node {
  458. array = append(array, item.ToMap())
  459. }
  460. result[k] = array
  461. case *Tree:
  462. result[k] = node.ToMap()
  463. case *tomlValue:
  464. result[k] = node.value
  465. }
  466. }
  467. return result
  468. }