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.

434 lines
10 KiB

  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. type valueComplexity int
  14. const (
  15. valueSimple valueComplexity = iota + 1
  16. valueComplex
  17. )
  18. type sortNode struct {
  19. key string
  20. complexity valueComplexity
  21. }
  22. // Encodes a string to a TOML-compliant multi-line string value
  23. // This function is a clone of the existing encodeTomlString function, except that whitespace characters
  24. // are preserved. Quotation marks and backslashes are also not escaped.
  25. func encodeMultilineTomlString(value string) string {
  26. var b bytes.Buffer
  27. for _, rr := range value {
  28. switch rr {
  29. case '\b':
  30. b.WriteString(`\b`)
  31. case '\t':
  32. b.WriteString("\t")
  33. case '\n':
  34. b.WriteString("\n")
  35. case '\f':
  36. b.WriteString(`\f`)
  37. case '\r':
  38. b.WriteString("\r")
  39. case '"':
  40. b.WriteString(`"`)
  41. case '\\':
  42. b.WriteString(`\`)
  43. default:
  44. intRr := uint16(rr)
  45. if intRr < 0x001F {
  46. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  47. } else {
  48. b.WriteRune(rr)
  49. }
  50. }
  51. }
  52. return b.String()
  53. }
  54. // Encodes a string to a TOML-compliant string value
  55. func encodeTomlString(value string) string {
  56. var b bytes.Buffer
  57. for _, rr := range value {
  58. switch rr {
  59. case '\b':
  60. b.WriteString(`\b`)
  61. case '\t':
  62. b.WriteString(`\t`)
  63. case '\n':
  64. b.WriteString(`\n`)
  65. case '\f':
  66. b.WriteString(`\f`)
  67. case '\r':
  68. b.WriteString(`\r`)
  69. case '"':
  70. b.WriteString(`\"`)
  71. case '\\':
  72. b.WriteString(`\\`)
  73. default:
  74. intRr := uint16(rr)
  75. if intRr < 0x001F {
  76. b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
  77. } else {
  78. b.WriteRune(rr)
  79. }
  80. }
  81. }
  82. return b.String()
  83. }
  84. func tomlValueStringRepresentation(v interface{}, indent string, arraysOneElementPerLine bool) (string, error) {
  85. // this interface check is added to dereference the change made in the writeTo function.
  86. // That change was made to allow this function to see formatting options.
  87. tv, ok := v.(*tomlValue)
  88. if ok {
  89. v = tv.value
  90. } else {
  91. tv = &tomlValue{}
  92. }
  93. switch value := v.(type) {
  94. case uint64:
  95. return strconv.FormatUint(value, 10), nil
  96. case int64:
  97. return strconv.FormatInt(value, 10), nil
  98. case float64:
  99. // Ensure a round float does contain a decimal point. Otherwise feeding
  100. // the output back to the parser would convert to an integer.
  101. if math.Trunc(value) == value {
  102. return strings.ToLower(strconv.FormatFloat(value, 'f', 1, 32)), nil
  103. }
  104. return strings.ToLower(strconv.FormatFloat(value, 'f', -1, 32)), nil
  105. case string:
  106. if tv.multiline {
  107. return "\"\"\"\n" + encodeMultilineTomlString(value) + "\"\"\"", nil
  108. }
  109. return "\"" + encodeTomlString(value) + "\"", nil
  110. case []byte:
  111. b, _ := v.([]byte)
  112. return tomlValueStringRepresentation(string(b), indent, arraysOneElementPerLine)
  113. case bool:
  114. if value {
  115. return "true", nil
  116. }
  117. return "false", nil
  118. case time.Time:
  119. return value.Format(time.RFC3339), nil
  120. case nil:
  121. return "", nil
  122. }
  123. rv := reflect.ValueOf(v)
  124. if rv.Kind() == reflect.Slice {
  125. var values []string
  126. for i := 0; i < rv.Len(); i++ {
  127. item := rv.Index(i).Interface()
  128. itemRepr, err := tomlValueStringRepresentation(item, indent, arraysOneElementPerLine)
  129. if err != nil {
  130. return "", err
  131. }
  132. values = append(values, itemRepr)
  133. }
  134. if arraysOneElementPerLine && len(values) > 1 {
  135. stringBuffer := bytes.Buffer{}
  136. valueIndent := indent + ` ` // TODO: move that to a shared encoder state
  137. stringBuffer.WriteString("[\n")
  138. for _, value := range values {
  139. stringBuffer.WriteString(valueIndent)
  140. stringBuffer.WriteString(value)
  141. stringBuffer.WriteString(`,`)
  142. stringBuffer.WriteString("\n")
  143. }
  144. stringBuffer.WriteString(indent + "]")
  145. return stringBuffer.String(), nil
  146. }
  147. return "[" + strings.Join(values, ",") + "]", nil
  148. }
  149. return "", fmt.Errorf("unsupported value type %T: %v", v, v)
  150. }
  151. func getTreeArrayLine(trees []*Tree) (line int) {
  152. // get lowest line number that is not 0
  153. for _, tv := range trees {
  154. if tv.position.Line < line || line == 0 {
  155. line = tv.position.Line
  156. }
  157. }
  158. return
  159. }
  160. func sortByLines(t *Tree) (vals []sortNode) {
  161. var (
  162. line int
  163. lines []int
  164. tv *Tree
  165. tom *tomlValue
  166. node sortNode
  167. )
  168. vals = make([]sortNode, 0)
  169. m := make(map[int]sortNode)
  170. for k := range t.values {
  171. v := t.values[k]
  172. switch v.(type) {
  173. case *Tree:
  174. tv = v.(*Tree)
  175. line = tv.position.Line
  176. node = sortNode{key: k, complexity: valueComplex}
  177. case []*Tree:
  178. line = getTreeArrayLine(v.([]*Tree))
  179. node = sortNode{key: k, complexity: valueComplex}
  180. default:
  181. tom = v.(*tomlValue)
  182. line = tom.position.Line
  183. node = sortNode{key: k, complexity: valueSimple}
  184. }
  185. lines = append(lines, line)
  186. vals = append(vals, node)
  187. m[line] = node
  188. }
  189. sort.Ints(lines)
  190. for i, line := range lines {
  191. vals[i] = m[line]
  192. }
  193. return vals
  194. }
  195. func sortAlphabetical(t *Tree) (vals []sortNode) {
  196. var (
  197. node sortNode
  198. simpVals []string
  199. compVals []string
  200. )
  201. vals = make([]sortNode, 0)
  202. m := make(map[string]sortNode)
  203. for k := range t.values {
  204. v := t.values[k]
  205. switch v.(type) {
  206. case *Tree, []*Tree:
  207. node = sortNode{key: k, complexity: valueComplex}
  208. compVals = append(compVals, node.key)
  209. default:
  210. node = sortNode{key: k, complexity: valueSimple}
  211. simpVals = append(simpVals, node.key)
  212. }
  213. vals = append(vals, node)
  214. m[node.key] = node
  215. }
  216. // Simples first to match previous implementation
  217. sort.Strings(simpVals)
  218. i := 0
  219. for _, key := range simpVals {
  220. vals[i] = m[key]
  221. i++
  222. }
  223. sort.Strings(compVals)
  224. for _, key := range compVals {
  225. vals[i] = m[key]
  226. i++
  227. }
  228. return vals
  229. }
  230. func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
  231. return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical)
  232. }
  233. func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder) (int64, error) {
  234. var orderedVals []sortNode
  235. switch ord {
  236. case OrderPreserve:
  237. orderedVals = sortByLines(t)
  238. default:
  239. orderedVals = sortAlphabetical(t)
  240. }
  241. for _, node := range orderedVals {
  242. switch node.complexity {
  243. case valueComplex:
  244. k := node.key
  245. v := t.values[k]
  246. combinedKey := k
  247. if keyspace != "" {
  248. combinedKey = keyspace + "." + combinedKey
  249. }
  250. var commented string
  251. if t.commented {
  252. commented = "# "
  253. }
  254. switch node := v.(type) {
  255. // node has to be of those two types given how keys are sorted above
  256. case *Tree:
  257. tv, ok := t.values[k].(*Tree)
  258. if !ok {
  259. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  260. }
  261. if tv.comment != "" {
  262. comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
  263. start := "# "
  264. if strings.HasPrefix(comment, "#") {
  265. start = ""
  266. }
  267. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
  268. bytesCount += int64(writtenBytesCountComment)
  269. if errc != nil {
  270. return bytesCount, errc
  271. }
  272. }
  273. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
  274. bytesCount += int64(writtenBytesCount)
  275. if err != nil {
  276. return bytesCount, err
  277. }
  278. bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
  279. if err != nil {
  280. return bytesCount, err
  281. }
  282. case []*Tree:
  283. for _, subTree := range node {
  284. writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
  285. bytesCount += int64(writtenBytesCount)
  286. if err != nil {
  287. return bytesCount, err
  288. }
  289. bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord)
  290. if err != nil {
  291. return bytesCount, err
  292. }
  293. }
  294. }
  295. default: // Simple
  296. k := node.key
  297. v, ok := t.values[k].(*tomlValue)
  298. if !ok {
  299. return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
  300. }
  301. repr, err := tomlValueStringRepresentation(v, indent, arraysOneElementPerLine)
  302. if err != nil {
  303. return bytesCount, err
  304. }
  305. if v.comment != "" {
  306. comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
  307. start := "# "
  308. if strings.HasPrefix(comment, "#") {
  309. start = ""
  310. }
  311. writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment, "\n")
  312. bytesCount += int64(writtenBytesCountComment)
  313. if errc != nil {
  314. return bytesCount, errc
  315. }
  316. }
  317. var commented string
  318. if v.commented {
  319. commented = "# "
  320. }
  321. writtenBytesCount, err := writeStrings(w, indent, commented, k, " = ", repr, "\n")
  322. bytesCount += int64(writtenBytesCount)
  323. if err != nil {
  324. return bytesCount, err
  325. }
  326. }
  327. }
  328. return bytesCount, nil
  329. }
  330. func writeStrings(w io.Writer, s ...string) (int, error) {
  331. var n int
  332. for i := range s {
  333. b, err := io.WriteString(w, s[i])
  334. n += b
  335. if err != nil {
  336. return n, err
  337. }
  338. }
  339. return n, nil
  340. }
  341. // WriteTo encode the Tree as Toml and writes it to the writer w.
  342. // Returns the number of bytes written in case of success, or an error if anything happened.
  343. func (t *Tree) WriteTo(w io.Writer) (int64, error) {
  344. return t.writeTo(w, "", "", 0, false)
  345. }
  346. // ToTomlString generates a human-readable representation of the current tree.
  347. // Output spans multiple lines, and is suitable for ingest by a TOML parser.
  348. // If the conversion cannot be performed, ToString returns a non-nil error.
  349. func (t *Tree) ToTomlString() (string, error) {
  350. var buf bytes.Buffer
  351. _, err := t.WriteTo(&buf)
  352. if err != nil {
  353. return "", err
  354. }
  355. return buf.String(), nil
  356. }
  357. // String generates a human-readable representation of the current tree.
  358. // Alias of ToString. Present to implement the fmt.Stringer interface.
  359. func (t *Tree) String() string {
  360. result, _ := t.ToTomlString()
  361. return result
  362. }
  363. // ToMap recursively generates a representation of the tree using Go built-in structures.
  364. // The following types are used:
  365. //
  366. // * bool
  367. // * float64
  368. // * int64
  369. // * string
  370. // * uint64
  371. // * time.Time
  372. // * map[string]interface{} (where interface{} is any of this list)
  373. // * []interface{} (where interface{} is any of this list)
  374. func (t *Tree) ToMap() map[string]interface{} {
  375. result := map[string]interface{}{}
  376. for k, v := range t.values {
  377. switch node := v.(type) {
  378. case []*Tree:
  379. var array []interface{}
  380. for _, item := range node {
  381. array = append(array, item.ToMap())
  382. }
  383. result[k] = array
  384. case *Tree:
  385. result[k] = node.ToMap()
  386. case *tomlValue:
  387. result[k] = node.value
  388. }
  389. }
  390. return result
  391. }