1package toml 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "reflect" 8 "sort" 9 "strconv" 10 "strings" 11 "time" 12) 13 14// encodes a string to a TOML-compliant string value 15func encodeTomlString(value string) string { 16 result := "" 17 for _, rr := range value { 18 switch rr { 19 case '\b': 20 result += "\\b" 21 case '\t': 22 result += "\\t" 23 case '\n': 24 result += "\\n" 25 case '\f': 26 result += "\\f" 27 case '\r': 28 result += "\\r" 29 case '"': 30 result += "\\\"" 31 case '\\': 32 result += "\\\\" 33 default: 34 intRr := uint16(rr) 35 if intRr < 0x001F { 36 result += fmt.Sprintf("\\u%0.4X", intRr) 37 } else { 38 result += string(rr) 39 } 40 } 41 } 42 return result 43} 44 45func tomlValueStringRepresentation(v interface{}) (string, error) { 46 switch value := v.(type) { 47 case uint64: 48 return strconv.FormatUint(value, 10), nil 49 case int64: 50 return strconv.FormatInt(value, 10), nil 51 case float64: 52 return strconv.FormatFloat(value, 'f', -1, 32), nil 53 case string: 54 return "\"" + encodeTomlString(value) + "\"", nil 55 case []byte: 56 b, _ := v.([]byte) 57 return tomlValueStringRepresentation(string(b)) 58 case bool: 59 if value { 60 return "true", nil 61 } 62 return "false", nil 63 case time.Time: 64 return value.Format(time.RFC3339), nil 65 case nil: 66 return "", nil 67 } 68 69 rv := reflect.ValueOf(v) 70 71 if rv.Kind() == reflect.Slice { 72 values := []string{} 73 for i := 0; i < rv.Len(); i++ { 74 item := rv.Index(i).Interface() 75 itemRepr, err := tomlValueStringRepresentation(item) 76 if err != nil { 77 return "", err 78 } 79 values = append(values, itemRepr) 80 } 81 return "[" + strings.Join(values, ",") + "]", nil 82 } 83 return "", fmt.Errorf("unsupported value type %T: %v", v, v) 84} 85 86func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64) (int64, error) { 87 simpleValuesKeys := make([]string, 0) 88 complexValuesKeys := make([]string, 0) 89 90 for k := range t.values { 91 v := t.values[k] 92 switch v.(type) { 93 case *Tree, []*Tree: 94 complexValuesKeys = append(complexValuesKeys, k) 95 default: 96 simpleValuesKeys = append(simpleValuesKeys, k) 97 } 98 } 99 100 sort.Strings(simpleValuesKeys) 101 sort.Strings(complexValuesKeys) 102 103 for _, k := range simpleValuesKeys { 104 v, ok := t.values[k].(*tomlValue) 105 if !ok { 106 return bytesCount, fmt.Errorf("invalid value type at %s: %T", k, t.values[k]) 107 } 108 109 repr, err := tomlValueStringRepresentation(v.value) 110 if err != nil { 111 return bytesCount, err 112 } 113 114 kvRepr := fmt.Sprintf("%s%s = %s\n", indent, k, repr) 115 writtenBytesCount, err := w.Write([]byte(kvRepr)) 116 bytesCount += int64(writtenBytesCount) 117 if err != nil { 118 return bytesCount, err 119 } 120 } 121 122 for _, k := range complexValuesKeys { 123 v := t.values[k] 124 125 combinedKey := k 126 if keyspace != "" { 127 combinedKey = keyspace + "." + combinedKey 128 } 129 130 switch node := v.(type) { 131 // node has to be of those two types given how keys are sorted above 132 case *Tree: 133 tableName := fmt.Sprintf("\n%s[%s]\n", indent, combinedKey) 134 writtenBytesCount, err := w.Write([]byte(tableName)) 135 bytesCount += int64(writtenBytesCount) 136 if err != nil { 137 return bytesCount, err 138 } 139 bytesCount, err = node.writeTo(w, indent+" ", combinedKey, bytesCount) 140 if err != nil { 141 return bytesCount, err 142 } 143 case []*Tree: 144 for _, subTree := range node { 145 tableArrayName := fmt.Sprintf("\n%s[[%s]]\n", indent, combinedKey) 146 writtenBytesCount, err := w.Write([]byte(tableArrayName)) 147 bytesCount += int64(writtenBytesCount) 148 if err != nil { 149 return bytesCount, err 150 } 151 152 bytesCount, err = subTree.writeTo(w, indent+" ", combinedKey, bytesCount) 153 if err != nil { 154 return bytesCount, err 155 } 156 } 157 } 158 } 159 160 return bytesCount, nil 161} 162 163// WriteTo encode the Tree as Toml and writes it to the writer w. 164// Returns the number of bytes written in case of success, or an error if anything happened. 165func (t *Tree) WriteTo(w io.Writer) (int64, error) { 166 return t.writeTo(w, "", "", 0) 167} 168 169// ToTomlString generates a human-readable representation of the current tree. 170// Output spans multiple lines, and is suitable for ingest by a TOML parser. 171// If the conversion cannot be performed, ToString returns a non-nil error. 172func (t *Tree) ToTomlString() (string, error) { 173 var buf bytes.Buffer 174 _, err := t.WriteTo(&buf) 175 if err != nil { 176 return "", err 177 } 178 return buf.String(), nil 179} 180 181// String generates a human-readable representation of the current tree. 182// Alias of ToString. Present to implement the fmt.Stringer interface. 183func (t *Tree) String() string { 184 result, _ := t.ToTomlString() 185 return result 186} 187 188// ToMap recursively generates a representation of the tree using Go built-in structures. 189// The following types are used: 190// 191// * bool 192// * float64 193// * int64 194// * string 195// * uint64 196// * time.Time 197// * map[string]interface{} (where interface{} is any of this list) 198// * []interface{} (where interface{} is any of this list) 199func (t *Tree) ToMap() map[string]interface{} { 200 result := map[string]interface{}{} 201 202 for k, v := range t.values { 203 switch node := v.(type) { 204 case []*Tree: 205 var array []interface{} 206 for _, item := range node { 207 array = append(array, item.ToMap()) 208 } 209 result[k] = array 210 case *Tree: 211 result[k] = node.ToMap() 212 case *tomlValue: 213 result[k] = node.value 214 } 215 } 216 return result 217} 218