gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/BurntSushi/toml/encode.go (about) 1 package toml 2 3 import ( 4 "bufio" 5 "errors" 6 "fmt" 7 "io" 8 "reflect" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 ) 14 15 type tomlEncodeError struct{ error } 16 17 var ( 18 errArrayMixedElementTypes = errors.New( 19 "toml: cannot encode array with mixed element types") 20 errArrayNilElement = errors.New( 21 "toml: cannot encode array with nil element") 22 errNonString = errors.New( 23 "toml: cannot encode a map with non-string key type") 24 errAnonNonStruct = errors.New( 25 "toml: cannot encode an anonymous field that is not a struct") 26 errArrayNoTable = errors.New( 27 "toml: TOML array element cannot contain a table") 28 errNoKey = errors.New( 29 "toml: top-level values must be Go maps or structs") 30 errAnything = errors.New("") // used in testing 31 ) 32 33 var quotedReplacer = strings.NewReplacer( 34 "\t", "\\t", 35 "\n", "\\n", 36 "\r", "\\r", 37 "\"", "\\\"", 38 "\\", "\\\\", 39 ) 40 41 // Encoder controls the encoding of Go values to a TOML document to some 42 // io.Writer. 43 // 44 // The indentation level can be controlled with the Indent field. 45 type Encoder struct { 46 // A single indentation level. By default it is two spaces. 47 Indent string 48 49 // hasWritten is whether we have written any output to w yet. 50 hasWritten bool 51 w *bufio.Writer 52 } 53 54 // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer 55 // given. By default, a single indentation level is 2 spaces. 56 func NewEncoder(w io.Writer) *Encoder { 57 return &Encoder{ 58 w: bufio.NewWriter(w), 59 Indent: " ", 60 } 61 } 62 63 // Encode writes a TOML representation of the Go value to the underlying 64 // io.Writer. If the value given cannot be encoded to a valid TOML document, 65 // then an error is returned. 66 // 67 // The mapping between Go values and TOML values should be precisely the same 68 // as for the Decode* functions. Similarly, the TextMarshaler interface is 69 // supported by encoding the resulting bytes as strings. (If you want to write 70 // arbitrary binary data then you will need to use something like base64 since 71 // TOML does not have any binary types.) 72 // 73 // When encoding TOML hashes (i.e., Go maps or structs), keys without any 74 // sub-hashes are encoded first. 75 // 76 // If a Go map is encoded, then its keys are sorted alphabetically for 77 // deterministic output. More control over this behavior may be provided if 78 // there is demand for it. 79 // 80 // Encoding Go values without a corresponding TOML representation---like map 81 // types with non-string keys---will cause an error to be returned. Similarly 82 // for mixed arrays/slices, arrays/slices with nil elements, embedded 83 // non-struct types and nested slices containing maps or structs. 84 // (e.g., [][]map[string]string is not allowed but []map[string]string is OK 85 // and so is []map[string][]string.) 86 func (enc *Encoder) Encode(v interface{}) error { 87 rv := eindirect(reflect.ValueOf(v)) 88 if err := enc.safeEncode(Key([]string{}), rv); err != nil { 89 return err 90 } 91 return enc.w.Flush() 92 } 93 94 func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { 95 defer func() { 96 if r := recover(); r != nil { 97 if terr, ok := r.(tomlEncodeError); ok { 98 err = terr.error 99 return 100 } 101 panic(r) 102 } 103 }() 104 enc.encode(key, rv) 105 return nil 106 } 107 108 func (enc *Encoder) encode(key Key, rv reflect.Value) { 109 // Special case. Time needs to be in ISO8601 format. 110 // Special case. If we can marshal the type to text, then we used that. 111 // Basically, this prevents the encoder for handling these types as 112 // generic structs (or whatever the underlying type of a TextMarshaler is). 113 switch rv.Interface().(type) { 114 case time.Time, TextMarshaler: 115 enc.keyEqElement(key, rv) 116 return 117 } 118 119 k := rv.Kind() 120 switch k { 121 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 122 reflect.Int64, 123 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 124 reflect.Uint64, 125 reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: 126 enc.keyEqElement(key, rv) 127 case reflect.Array, reflect.Slice: 128 if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { 129 enc.eArrayOfTables(key, rv) 130 } else { 131 enc.keyEqElement(key, rv) 132 } 133 case reflect.Interface: 134 if rv.IsNil() { 135 return 136 } 137 enc.encode(key, rv.Elem()) 138 case reflect.Map: 139 if rv.IsNil() { 140 return 141 } 142 enc.eTable(key, rv) 143 case reflect.Ptr: 144 if rv.IsNil() { 145 return 146 } 147 enc.encode(key, rv.Elem()) 148 case reflect.Struct: 149 enc.eTable(key, rv) 150 default: 151 panic(e("unsupported type for key '%s': %s", key, k)) 152 } 153 } 154 155 // eElement encodes any value that can be an array element (primitives and 156 // arrays). 157 func (enc *Encoder) eElement(rv reflect.Value) { 158 switch v := rv.Interface().(type) { 159 case time.Time: 160 // Special case time.Time as a primitive. Has to come before 161 // TextMarshaler below because time.Time implements 162 // encoding.TextMarshaler, but we need to always use UTC. 163 enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) 164 return 165 case TextMarshaler: 166 // Special case. Use text marshaler if it's available for this value. 167 if s, err := v.MarshalText(); err != nil { 168 encPanic(err) 169 } else { 170 enc.writeQuoted(string(s)) 171 } 172 return 173 } 174 switch rv.Kind() { 175 case reflect.Bool: 176 enc.wf(strconv.FormatBool(rv.Bool())) 177 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 178 reflect.Int64: 179 enc.wf(strconv.FormatInt(rv.Int(), 10)) 180 case reflect.Uint, reflect.Uint8, reflect.Uint16, 181 reflect.Uint32, reflect.Uint64: 182 enc.wf(strconv.FormatUint(rv.Uint(), 10)) 183 case reflect.Float32: 184 enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) 185 case reflect.Float64: 186 enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) 187 case reflect.Array, reflect.Slice: 188 enc.eArrayOrSliceElement(rv) 189 case reflect.Interface: 190 enc.eElement(rv.Elem()) 191 case reflect.String: 192 enc.writeQuoted(rv.String()) 193 default: 194 panic(e("unexpected primitive type: %s", rv.Kind())) 195 } 196 } 197 198 // By the TOML spec, all floats must have a decimal with at least one 199 // number on either side. 200 func floatAddDecimal(fstr string) string { 201 if !strings.Contains(fstr, ".") { 202 return fstr + ".0" 203 } 204 return fstr 205 } 206 207 func (enc *Encoder) writeQuoted(s string) { 208 enc.wf("\"%s\"", quotedReplacer.Replace(s)) 209 } 210 211 func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { 212 length := rv.Len() 213 enc.wf("[") 214 for i := 0; i < length; i++ { 215 elem := rv.Index(i) 216 enc.eElement(elem) 217 if i != length-1 { 218 enc.wf(", ") 219 } 220 } 221 enc.wf("]") 222 } 223 224 func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { 225 if len(key) == 0 { 226 encPanic(errNoKey) 227 } 228 for i := 0; i < rv.Len(); i++ { 229 trv := rv.Index(i) 230 if isNil(trv) { 231 continue 232 } 233 panicIfInvalidKey(key) 234 enc.newline() 235 enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) 236 enc.newline() 237 enc.eMapOrStruct(key, trv) 238 } 239 } 240 241 func (enc *Encoder) eTable(key Key, rv reflect.Value) { 242 panicIfInvalidKey(key) 243 if len(key) == 1 { 244 // Output an extra newline between top-level tables. 245 // (The newline isn't written if nothing else has been written though.) 246 enc.newline() 247 } 248 if len(key) > 0 { 249 enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) 250 enc.newline() 251 } 252 enc.eMapOrStruct(key, rv) 253 } 254 255 func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { 256 switch rv := eindirect(rv); rv.Kind() { 257 case reflect.Map: 258 enc.eMap(key, rv) 259 case reflect.Struct: 260 enc.eStruct(key, rv) 261 default: 262 panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) 263 } 264 } 265 266 func (enc *Encoder) eMap(key Key, rv reflect.Value) { 267 rt := rv.Type() 268 if rt.Key().Kind() != reflect.String { 269 encPanic(errNonString) 270 } 271 272 // Sort keys so that we have deterministic output. And write keys directly 273 // underneath this key first, before writing sub-structs or sub-maps. 274 var mapKeysDirect, mapKeysSub []string 275 for _, mapKey := range rv.MapKeys() { 276 k := mapKey.String() 277 if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { 278 mapKeysSub = append(mapKeysSub, k) 279 } else { 280 mapKeysDirect = append(mapKeysDirect, k) 281 } 282 } 283 284 var writeMapKeys = func(mapKeys []string) { 285 sort.Strings(mapKeys) 286 for _, mapKey := range mapKeys { 287 mrv := rv.MapIndex(reflect.ValueOf(mapKey)) 288 if isNil(mrv) { 289 // Don't write anything for nil fields. 290 continue 291 } 292 enc.encode(key.add(mapKey), mrv) 293 } 294 } 295 writeMapKeys(mapKeysDirect) 296 writeMapKeys(mapKeysSub) 297 } 298 299 func (enc *Encoder) eStruct(key Key, rv reflect.Value) { 300 // Write keys for fields directly under this key first, because if we write 301 // a field that creates a new table, then all keys under it will be in that 302 // table (not the one we're writing here). 303 rt := rv.Type() 304 var fieldsDirect, fieldsSub [][]int 305 var addFields func(rt reflect.Type, rv reflect.Value, start []int) 306 addFields = func(rt reflect.Type, rv reflect.Value, start []int) { 307 for i := 0; i < rt.NumField(); i++ { 308 f := rt.Field(i) 309 // skip unexported fields 310 if f.PkgPath != "" && !f.Anonymous { 311 continue 312 } 313 frv := rv.Field(i) 314 if f.Anonymous { 315 t := f.Type 316 switch t.Kind() { 317 case reflect.Struct: 318 // Treat anonymous struct fields with 319 // tag names as though they are not 320 // anonymous, like encoding/json does. 321 if getOptions(f.Tag).name == "" { 322 addFields(t, frv, f.Index) 323 continue 324 } 325 case reflect.Ptr: 326 if t.Elem().Kind() == reflect.Struct && 327 getOptions(f.Tag).name == "" { 328 if !frv.IsNil() { 329 addFields(t.Elem(), frv.Elem(), f.Index) 330 } 331 continue 332 } 333 // Fall through to the normal field encoding logic below 334 // for non-struct anonymous fields. 335 } 336 } 337 338 if typeIsHash(tomlTypeOfGo(frv)) { 339 fieldsSub = append(fieldsSub, append(start, f.Index...)) 340 } else { 341 fieldsDirect = append(fieldsDirect, append(start, f.Index...)) 342 } 343 } 344 } 345 addFields(rt, rv, nil) 346 347 var writeFields = func(fields [][]int) { 348 for _, fieldIndex := range fields { 349 sft := rt.FieldByIndex(fieldIndex) 350 sf := rv.FieldByIndex(fieldIndex) 351 if isNil(sf) { 352 // Don't write anything for nil fields. 353 continue 354 } 355 356 opts := getOptions(sft.Tag) 357 if opts.skip { 358 continue 359 } 360 keyName := sft.Name 361 if opts.name != "" { 362 keyName = opts.name 363 } 364 if opts.omitempty && isEmpty(sf) { 365 continue 366 } 367 if opts.omitzero && isZero(sf) { 368 continue 369 } 370 371 enc.encode(key.add(keyName), sf) 372 } 373 } 374 writeFields(fieldsDirect) 375 writeFields(fieldsSub) 376 } 377 378 // tomlTypeName returns the TOML type name of the Go value's type. It is 379 // used to determine whether the types of array elements are mixed (which is 380 // forbidden). If the Go value is nil, then it is illegal for it to be an array 381 // element, and valueIsNil is returned as true. 382 383 // Returns the TOML type of a Go value. The type may be `nil`, which means 384 // no concrete TOML type could be found. 385 func tomlTypeOfGo(rv reflect.Value) tomlType { 386 if isNil(rv) || !rv.IsValid() { 387 return nil 388 } 389 switch rv.Kind() { 390 case reflect.Bool: 391 return tomlBool 392 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 393 reflect.Int64, 394 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 395 reflect.Uint64: 396 return tomlInteger 397 case reflect.Float32, reflect.Float64: 398 return tomlFloat 399 case reflect.Array, reflect.Slice: 400 if typeEqual(tomlHash, tomlArrayType(rv)) { 401 return tomlArrayHash 402 } 403 return tomlArray 404 case reflect.Ptr, reflect.Interface: 405 return tomlTypeOfGo(rv.Elem()) 406 case reflect.String: 407 return tomlString 408 case reflect.Map: 409 return tomlHash 410 case reflect.Struct: 411 switch rv.Interface().(type) { 412 case time.Time: 413 return tomlDatetime 414 case TextMarshaler: 415 return tomlString 416 default: 417 return tomlHash 418 } 419 default: 420 panic("unexpected reflect.Kind: " + rv.Kind().String()) 421 } 422 } 423 424 // tomlArrayType returns the element type of a TOML array. The type returned 425 // may be nil if it cannot be determined (e.g., a nil slice or a zero length 426 // slize). This function may also panic if it finds a type that cannot be 427 // expressed in TOML (such as nil elements, heterogeneous arrays or directly 428 // nested arrays of tables). 429 func tomlArrayType(rv reflect.Value) tomlType { 430 if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { 431 return nil 432 } 433 firstType := tomlTypeOfGo(rv.Index(0)) 434 if firstType == nil { 435 encPanic(errArrayNilElement) 436 } 437 438 rvlen := rv.Len() 439 for i := 1; i < rvlen; i++ { 440 elem := rv.Index(i) 441 switch elemType := tomlTypeOfGo(elem); { 442 case elemType == nil: 443 encPanic(errArrayNilElement) 444 case !typeEqual(firstType, elemType): 445 encPanic(errArrayMixedElementTypes) 446 } 447 } 448 // If we have a nested array, then we must make sure that the nested 449 // array contains ONLY primitives. 450 // This checks arbitrarily nested arrays. 451 if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { 452 nest := tomlArrayType(eindirect(rv.Index(0))) 453 if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { 454 encPanic(errArrayNoTable) 455 } 456 } 457 return firstType 458 } 459 460 type tagOptions struct { 461 skip bool // "-" 462 name string 463 omitempty bool 464 omitzero bool 465 } 466 467 func getOptions(tag reflect.StructTag) tagOptions { 468 t := tag.Get("toml") 469 if t == "-" { 470 return tagOptions{skip: true} 471 } 472 var opts tagOptions 473 parts := strings.Split(t, ",") 474 opts.name = parts[0] 475 for _, s := range parts[1:] { 476 switch s { 477 case "omitempty": 478 opts.omitempty = true 479 case "omitzero": 480 opts.omitzero = true 481 } 482 } 483 return opts 484 } 485 486 func isZero(rv reflect.Value) bool { 487 switch rv.Kind() { 488 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 489 return rv.Int() == 0 490 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 491 return rv.Uint() == 0 492 case reflect.Float32, reflect.Float64: 493 return rv.Float() == 0.0 494 } 495 return false 496 } 497 498 func isEmpty(rv reflect.Value) bool { 499 switch rv.Kind() { 500 case reflect.Array, reflect.Slice, reflect.Map, reflect.String: 501 return rv.Len() == 0 502 case reflect.Bool: 503 return !rv.Bool() 504 } 505 return false 506 } 507 508 func (enc *Encoder) newline() { 509 if enc.hasWritten { 510 enc.wf("\n") 511 } 512 } 513 514 func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { 515 if len(key) == 0 { 516 encPanic(errNoKey) 517 } 518 panicIfInvalidKey(key) 519 enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) 520 enc.eElement(val) 521 enc.newline() 522 } 523 524 func (enc *Encoder) wf(format string, v ...interface{}) { 525 if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { 526 encPanic(err) 527 } 528 enc.hasWritten = true 529 } 530 531 func (enc *Encoder) indentStr(key Key) string { 532 return strings.Repeat(enc.Indent, len(key)-1) 533 } 534 535 func encPanic(err error) { 536 panic(tomlEncodeError{err}) 537 } 538 539 func eindirect(v reflect.Value) reflect.Value { 540 switch v.Kind() { 541 case reflect.Ptr, reflect.Interface: 542 return eindirect(v.Elem()) 543 default: 544 return v 545 } 546 } 547 548 func isNil(rv reflect.Value) bool { 549 switch rv.Kind() { 550 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 551 return rv.IsNil() 552 default: 553 return false 554 } 555 } 556 557 func panicIfInvalidKey(key Key) { 558 for _, k := range key { 559 if len(k) == 0 { 560 encPanic(e("Key '%s' is not a valid table name. Key names "+ 561 "cannot be empty.", key.maybeQuotedAll())) 562 } 563 } 564 } 565 566 func isValidKeyName(s string) bool { 567 return len(s) != 0 568 }