1 package toml
2
3 import (
4 "bytes"
5 "fmt"
6 "io"
7 "math"
8 "math/big"
9 "reflect"
10 "sort"
11 "strconv"
12 "strings"
13 "time"
14 )
15
16 type valueComplexity int
17
18 const (
19 valueSimple valueComplexity = iota + 1
20 valueComplex
21 )
22
23 type sortNode struct {
24 key string
25 complexity valueComplexity
26 }
27
28
29
30
31 func encodeMultilineTomlString(value string, commented string) string {
32 var b bytes.Buffer
33 adjacentQuoteCount := 0
34
35 b.WriteString(commented)
36 for i, rr := range value {
37 if rr != '"' {
38 adjacentQuoteCount = 0
39 } else {
40 adjacentQuoteCount++
41 }
42 switch rr {
43 case '\b':
44 b.WriteString(`\b`)
45 case '\t':
46 b.WriteString("\t")
47 case '\n':
48 b.WriteString("\n" + commented)
49 case '\f':
50 b.WriteString(`\f`)
51 case '\r':
52 b.WriteString("\r")
53 case '"':
54 if adjacentQuoteCount >= 3 || i == len(value)-1 {
55 adjacentQuoteCount = 0
56 b.WriteString(`\"`)
57 } else {
58 b.WriteString(`"`)
59 }
60 case '\\':
61 b.WriteString(`\`)
62 default:
63 intRr := uint16(rr)
64 if intRr < 0x001F {
65 b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
66 } else {
67 b.WriteRune(rr)
68 }
69 }
70 }
71 return b.String()
72 }
73
74
75 func encodeTomlString(value string) string {
76 var b bytes.Buffer
77
78 for _, rr := range value {
79 switch rr {
80 case '\b':
81 b.WriteString(`\b`)
82 case '\t':
83 b.WriteString(`\t`)
84 case '\n':
85 b.WriteString(`\n`)
86 case '\f':
87 b.WriteString(`\f`)
88 case '\r':
89 b.WriteString(`\r`)
90 case '"':
91 b.WriteString(`\"`)
92 case '\\':
93 b.WriteString(`\\`)
94 default:
95 intRr := uint16(rr)
96 if intRr < 0x001F {
97 b.WriteString(fmt.Sprintf("\\u%0.4X", intRr))
98 } else {
99 b.WriteRune(rr)
100 }
101 }
102 }
103 return b.String()
104 }
105
106 func tomlTreeStringRepresentation(t *Tree, ord MarshalOrder) (string, error) {
107 var orderedVals []sortNode
108 switch ord {
109 case OrderPreserve:
110 orderedVals = sortByLines(t)
111 default:
112 orderedVals = sortAlphabetical(t)
113 }
114
115 var values []string
116 for _, node := range orderedVals {
117 k := node.key
118 v := t.values[k]
119
120 repr, err := tomlValueStringRepresentation(v, "", "", ord, false)
121 if err != nil {
122 return "", err
123 }
124 values = append(values, quoteKeyIfNeeded(k)+" = "+repr)
125 }
126 return "{ " + strings.Join(values, ", ") + " }", nil
127 }
128
129 func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord MarshalOrder, arraysOneElementPerLine bool) (string, error) {
130
131
132 tv, ok := v.(*tomlValue)
133 if ok {
134 v = tv.value
135 } else {
136 tv = &tomlValue{}
137 }
138
139 switch value := v.(type) {
140 case uint64:
141 return strconv.FormatUint(value, 10), nil
142 case int64:
143 return strconv.FormatInt(value, 10), nil
144 case float64:
145
146 bits := 64
147
148 if !math.IsNaN(value) {
149
150 _, acc := big.NewFloat(value).Float32()
151 if acc == big.Exact {
152 bits = 32
153 }
154 }
155 if math.Trunc(value) == value {
156 return strings.ToLower(strconv.FormatFloat(value, 'f', 1, bits)), nil
157 }
158 return strings.ToLower(strconv.FormatFloat(value, 'f', -1, bits)), nil
159 case string:
160 if tv.multiline {
161 if tv.literal {
162 b := strings.Builder{}
163 b.WriteString("'''\n")
164 b.Write([]byte(value))
165 b.WriteString("\n'''")
166 return b.String(), nil
167 } else {
168 return "\"\"\"\n" + encodeMultilineTomlString(value, commented) + "\"\"\"", nil
169 }
170 }
171 return "\"" + encodeTomlString(value) + "\"", nil
172 case []byte:
173 b, _ := v.([]byte)
174 return string(b), nil
175 case bool:
176 if value {
177 return "true", nil
178 }
179 return "false", nil
180 case time.Time:
181 return value.Format(time.RFC3339), nil
182 case LocalDate:
183 return value.String(), nil
184 case LocalDateTime:
185 return value.String(), nil
186 case LocalTime:
187 return value.String(), nil
188 case *Tree:
189 return tomlTreeStringRepresentation(value, ord)
190 case nil:
191 return "", nil
192 }
193
194 rv := reflect.ValueOf(v)
195
196 if rv.Kind() == reflect.Slice {
197 var values []string
198 for i := 0; i < rv.Len(); i++ {
199 item := rv.Index(i).Interface()
200 itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine)
201 if err != nil {
202 return "", err
203 }
204 values = append(values, itemRepr)
205 }
206 if arraysOneElementPerLine && len(values) > 1 {
207 stringBuffer := bytes.Buffer{}
208 valueIndent := indent + ` `
209
210 stringBuffer.WriteString("[\n")
211
212 for _, value := range values {
213 stringBuffer.WriteString(valueIndent)
214 stringBuffer.WriteString(commented + value)
215 stringBuffer.WriteString(`,`)
216 stringBuffer.WriteString("\n")
217 }
218
219 stringBuffer.WriteString(indent + commented + "]")
220
221 return stringBuffer.String(), nil
222 }
223 return "[" + strings.Join(values, ", ") + "]", nil
224 }
225 return "", fmt.Errorf("unsupported value type %T: %v", v, v)
226 }
227
228 func getTreeArrayLine(trees []*Tree) (line int) {
229
230 line = int(^uint(0) >> 1)
231
232 for _, tv := range trees {
233 if tv.position.Line < line || line == 0 {
234 line = tv.position.Line
235 }
236 }
237 return
238 }
239
240 func sortByLines(t *Tree) (vals []sortNode) {
241 var (
242 line int
243 lines []int
244 tv *Tree
245 tom *tomlValue
246 node sortNode
247 )
248 vals = make([]sortNode, 0)
249 m := make(map[int]sortNode)
250
251 for k := range t.values {
252 v := t.values[k]
253 switch v.(type) {
254 case *Tree:
255 tv = v.(*Tree)
256 line = tv.position.Line
257 node = sortNode{key: k, complexity: valueComplex}
258 case []*Tree:
259 line = getTreeArrayLine(v.([]*Tree))
260 node = sortNode{key: k, complexity: valueComplex}
261 default:
262 tom = v.(*tomlValue)
263 line = tom.position.Line
264 node = sortNode{key: k, complexity: valueSimple}
265 }
266 lines = append(lines, line)
267 vals = append(vals, node)
268 m[line] = node
269 }
270 sort.Ints(lines)
271
272 for i, line := range lines {
273 vals[i] = m[line]
274 }
275
276 return vals
277 }
278
279 func sortAlphabetical(t *Tree) (vals []sortNode) {
280 var (
281 node sortNode
282 simpVals []string
283 compVals []string
284 )
285 vals = make([]sortNode, 0)
286 m := make(map[string]sortNode)
287
288 for k := range t.values {
289 v := t.values[k]
290 switch v.(type) {
291 case *Tree, []*Tree:
292 node = sortNode{key: k, complexity: valueComplex}
293 compVals = append(compVals, node.key)
294 default:
295 node = sortNode{key: k, complexity: valueSimple}
296 simpVals = append(simpVals, node.key)
297 }
298 vals = append(vals, node)
299 m[node.key] = node
300 }
301
302
303 sort.Strings(simpVals)
304 i := 0
305 for _, key := range simpVals {
306 vals[i] = m[key]
307 i++
308 }
309
310 sort.Strings(compVals)
311 for _, key := range compVals {
312 vals[i] = m[key]
313 i++
314 }
315
316 return vals
317 }
318
319 func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
320 return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false, false)
321 }
322
323 func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord MarshalOrder, indentString string, compactComments, parentCommented bool) (int64, error) {
324 var orderedVals []sortNode
325
326 switch ord {
327 case OrderPreserve:
328 orderedVals = sortByLines(t)
329 default:
330 orderedVals = sortAlphabetical(t)
331 }
332
333 for _, node := range orderedVals {
334 switch node.complexity {
335 case valueComplex:
336 k := node.key
337 v := t.values[k]
338
339 combinedKey := quoteKeyIfNeeded(k)
340 if keyspace != "" {
341 combinedKey = keyspace + "." + combinedKey
342 }
343
344 switch node := v.(type) {
345
346 case *Tree:
347 tv, ok := t.values[k].(*Tree)
348 if !ok {
349 return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
350 }
351 if tv.comment != "" {
352 comment := strings.Replace(tv.comment, "\n", "\n"+indent+"#", -1)
353 start := "# "
354 if strings.HasPrefix(comment, "#") {
355 start = ""
356 }
357 writtenBytesCountComment, errc := writeStrings(w, "\n", indent, start, comment)
358 bytesCount += int64(writtenBytesCountComment)
359 if errc != nil {
360 return bytesCount, errc
361 }
362 }
363
364 var commented string
365 if parentCommented || t.commented || tv.commented {
366 commented = "# "
367 }
368 writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[", combinedKey, "]\n")
369 bytesCount += int64(writtenBytesCount)
370 if err != nil {
371 return bytesCount, err
372 }
373 bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || tv.commented)
374 if err != nil {
375 return bytesCount, err
376 }
377 case []*Tree:
378 for _, subTree := range node {
379 var commented string
380 if parentCommented || t.commented || subTree.commented {
381 commented = "# "
382 }
383 writtenBytesCount, err := writeStrings(w, "\n", indent, commented, "[[", combinedKey, "]]\n")
384 bytesCount += int64(writtenBytesCount)
385 if err != nil {
386 return bytesCount, err
387 }
388
389 bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, compactComments, parentCommented || t.commented || subTree.commented)
390 if err != nil {
391 return bytesCount, err
392 }
393 }
394 }
395 default:
396 k := node.key
397 v, ok := t.values[k].(*tomlValue)
398 if !ok {
399 return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k])
400 }
401
402 var commented string
403 if parentCommented || t.commented || v.commented {
404 commented = "# "
405 }
406 repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine)
407 if err != nil {
408 return bytesCount, err
409 }
410
411 if v.comment != "" {
412 comment := strings.Replace(v.comment, "\n", "\n"+indent+"#", -1)
413 start := "# "
414 if strings.HasPrefix(comment, "#") {
415 start = ""
416 }
417 if !compactComments {
418 writtenBytesCountComment, errc := writeStrings(w, "\n")
419 bytesCount += int64(writtenBytesCountComment)
420 if errc != nil {
421 return bytesCount, errc
422 }
423 }
424 writtenBytesCountComment, errc := writeStrings(w, indent, start, comment, "\n")
425 bytesCount += int64(writtenBytesCountComment)
426 if errc != nil {
427 return bytesCount, errc
428 }
429 }
430
431 quotedKey := quoteKeyIfNeeded(k)
432 writtenBytesCount, err := writeStrings(w, indent, commented, quotedKey, " = ", repr, "\n")
433 bytesCount += int64(writtenBytesCount)
434 if err != nil {
435 return bytesCount, err
436 }
437 }
438 }
439
440 return bytesCount, nil
441 }
442
443
444
445 func quoteKeyIfNeeded(k string) string {
446
447
448
449 if len(k) >= 2 && k[0] == '"' && k[len(k)-1] == '"' {
450 return k
451 }
452 isBare := true
453 for _, r := range k {
454 if !isValidBareChar(r) {
455 isBare = false
456 break
457 }
458 }
459 if isBare {
460 return k
461 }
462 return quoteKey(k)
463 }
464
465 func quoteKey(k string) string {
466 return "\"" + encodeTomlString(k) + "\""
467 }
468
469 func writeStrings(w io.Writer, s ...string) (int, error) {
470 var n int
471 for i := range s {
472 b, err := io.WriteString(w, s[i])
473 n += b
474 if err != nil {
475 return n, err
476 }
477 }
478 return n, nil
479 }
480
481
482
483 func (t *Tree) WriteTo(w io.Writer) (int64, error) {
484 return t.writeTo(w, "", "", 0, false)
485 }
486
487
488
489
490 func (t *Tree) ToTomlString() (string, error) {
491 b, err := t.Marshal()
492 if err != nil {
493 return "", err
494 }
495 return string(b), nil
496 }
497
498
499
500 func (t *Tree) String() string {
501 result, _ := t.ToTomlString()
502 return result
503 }
504
505
506
507
508
509
510
511
512
513
514
515
516 func (t *Tree) ToMap() map[string]interface{} {
517 result := map[string]interface{}{}
518
519 for k, v := range t.values {
520 switch node := v.(type) {
521 case []*Tree:
522 var array []interface{}
523 for _, item := range node {
524 array = append(array, item.ToMap())
525 }
526 result[k] = array
527 case *Tree:
528 result[k] = node.ToMap()
529 case *tomlValue:
530 result[k] = tomlValueToGo(node.value)
531 }
532 }
533 return result
534 }
535
536 func tomlValueToGo(v interface{}) interface{} {
537 if tree, ok := v.(*Tree); ok {
538 return tree.ToMap()
539 }
540
541 rv := reflect.ValueOf(v)
542
543 if rv.Kind() != reflect.Slice {
544 return v
545 }
546 values := make([]interface{}, rv.Len())
547 for i := 0; i < rv.Len(); i++ {
548 item := rv.Index(i).Interface()
549 values[i] = tomlValueToGo(item)
550 }
551 return values
552 }
553
View as plain text