1package toml 2 3import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io" 8 "reflect" 9 "strconv" 10 "strings" 11 "time" 12) 13 14const tagKeyMultiline = "multiline" 15 16type tomlOpts struct { 17 name string 18 comment string 19 commented bool 20 multiline bool 21 include bool 22 omitempty bool 23} 24 25type encOpts struct { 26 quoteMapKeys bool 27 arraysOneElementPerLine bool 28} 29 30var encOptsDefaults = encOpts{ 31 quoteMapKeys: false, 32} 33 34var timeType = reflect.TypeOf(time.Time{}) 35var marshalerType = reflect.TypeOf(new(Marshaler)).Elem() 36 37// Check if the given marshall type maps to a Tree primitive 38func isPrimitive(mtype reflect.Type) bool { 39 switch mtype.Kind() { 40 case reflect.Ptr: 41 return isPrimitive(mtype.Elem()) 42 case reflect.Bool: 43 return true 44 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 45 return true 46 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 47 return true 48 case reflect.Float32, reflect.Float64: 49 return true 50 case reflect.String: 51 return true 52 case reflect.Struct: 53 return mtype == timeType || isCustomMarshaler(mtype) 54 default: 55 return false 56 } 57} 58 59// Check if the given marshall type maps to a Tree slice 60func isTreeSlice(mtype reflect.Type) bool { 61 switch mtype.Kind() { 62 case reflect.Slice: 63 return !isOtherSlice(mtype) 64 default: 65 return false 66 } 67} 68 69// Check if the given marshall type maps to a non-Tree slice 70func isOtherSlice(mtype reflect.Type) bool { 71 switch mtype.Kind() { 72 case reflect.Ptr: 73 return isOtherSlice(mtype.Elem()) 74 case reflect.Slice: 75 return isPrimitive(mtype.Elem()) || isOtherSlice(mtype.Elem()) 76 default: 77 return false 78 } 79} 80 81// Check if the given marshall type maps to a Tree 82func isTree(mtype reflect.Type) bool { 83 switch mtype.Kind() { 84 case reflect.Map: 85 return true 86 case reflect.Struct: 87 return !isPrimitive(mtype) 88 default: 89 return false 90 } 91} 92 93func isCustomMarshaler(mtype reflect.Type) bool { 94 return mtype.Implements(marshalerType) 95} 96 97func callCustomMarshaler(mval reflect.Value) ([]byte, error) { 98 return mval.Interface().(Marshaler).MarshalTOML() 99} 100 101// Marshaler is the interface implemented by types that 102// can marshal themselves into valid TOML. 103type Marshaler interface { 104 MarshalTOML() ([]byte, error) 105} 106 107/* 108Marshal returns the TOML encoding of v. Behavior is similar to the Go json 109encoder, except that there is no concept of a Marshaler interface or MarshalTOML 110function for sub-structs, and currently only definite types can be marshaled 111(i.e. no `interface{}`). 112 113The following struct annotations are supported: 114 115 toml:"Field" Overrides the field's name to output. 116 omitempty When set, empty values and groups are not emitted. 117 comment:"comment" Emits a # comment on the same line. This supports new lines. 118 commented:"true" Emits the value as commented. 119 120Note that pointers are automatically assigned the "omitempty" option, as TOML 121explicitly does not handle null values (saying instead the label should be 122dropped). 123 124Tree structural types and corresponding marshal types: 125 126 *Tree (*)struct, (*)map[string]interface{} 127 []*Tree (*)[](*)struct, (*)[](*)map[string]interface{} 128 []interface{} (as interface{}) (*)[]primitive, (*)[]([]interface{}) 129 interface{} (*)primitive 130 131Tree primitive types and corresponding marshal types: 132 133 uint64 uint, uint8-uint64, pointers to same 134 int64 int, int8-uint64, pointers to same 135 float64 float32, float64, pointers to same 136 string string, pointers to same 137 bool bool, pointers to same 138 time.Time time.Time{}, pointers to same 139*/ 140func Marshal(v interface{}) ([]byte, error) { 141 return NewEncoder(nil).marshal(v) 142} 143 144// Encoder writes TOML values to an output stream. 145type Encoder struct { 146 w io.Writer 147 encOpts 148} 149 150// NewEncoder returns a new encoder that writes to w. 151func NewEncoder(w io.Writer) *Encoder { 152 return &Encoder{ 153 w: w, 154 encOpts: encOptsDefaults, 155 } 156} 157 158// Encode writes the TOML encoding of v to the stream. 159// 160// See the documentation for Marshal for details. 161func (e *Encoder) Encode(v interface{}) error { 162 b, err := e.marshal(v) 163 if err != nil { 164 return err 165 } 166 if _, err := e.w.Write(b); err != nil { 167 return err 168 } 169 return nil 170} 171 172// QuoteMapKeys sets up the encoder to encode 173// maps with string type keys with quoted TOML keys. 174// 175// This relieves the character limitations on map keys. 176func (e *Encoder) QuoteMapKeys(v bool) *Encoder { 177 e.quoteMapKeys = v 178 return e 179} 180 181// ArraysWithOneElementPerLine sets up the encoder to encode arrays 182// with more than one element on multiple lines instead of one. 183// 184// For example: 185// 186// A = [1,2,3] 187// 188// Becomes 189// 190// A = [ 191// 1, 192// 2, 193// 3, 194// ] 195func (e *Encoder) ArraysWithOneElementPerLine(v bool) *Encoder { 196 e.arraysOneElementPerLine = v 197 return e 198} 199 200func (e *Encoder) marshal(v interface{}) ([]byte, error) { 201 mtype := reflect.TypeOf(v) 202 if mtype.Kind() != reflect.Struct { 203 return []byte{}, errors.New("Only a struct can be marshaled to TOML") 204 } 205 sval := reflect.ValueOf(v) 206 if isCustomMarshaler(mtype) { 207 return callCustomMarshaler(sval) 208 } 209 t, err := e.valueToTree(mtype, sval) 210 if err != nil { 211 return []byte{}, err 212 } 213 214 var buf bytes.Buffer 215 _, err = t.writeTo(&buf, "", "", 0, e.arraysOneElementPerLine) 216 217 return buf.Bytes(), err 218} 219 220// Convert given marshal struct or map value to toml tree 221func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, error) { 222 if mtype.Kind() == reflect.Ptr { 223 return e.valueToTree(mtype.Elem(), mval.Elem()) 224 } 225 tval := newTree() 226 switch mtype.Kind() { 227 case reflect.Struct: 228 for i := 0; i < mtype.NumField(); i++ { 229 mtypef, mvalf := mtype.Field(i), mval.Field(i) 230 opts := tomlOptions(mtypef) 231 if opts.include && (!opts.omitempty || !isZero(mvalf)) { 232 val, err := e.valueToToml(mtypef.Type, mvalf) 233 if err != nil { 234 return nil, err 235 } 236 237 tval.SetWithOptions(opts.name, SetOptions{ 238 Comment: opts.comment, 239 Commented: opts.commented, 240 Multiline: opts.multiline, 241 }, val) 242 } 243 } 244 case reflect.Map: 245 for _, key := range mval.MapKeys() { 246 mvalf := mval.MapIndex(key) 247 val, err := e.valueToToml(mtype.Elem(), mvalf) 248 if err != nil { 249 return nil, err 250 } 251 if e.quoteMapKeys { 252 keyStr, err := tomlValueStringRepresentation(key.String(), "", e.arraysOneElementPerLine) 253 if err != nil { 254 return nil, err 255 } 256 tval.SetPath([]string{keyStr}, val) 257 } else { 258 tval.Set(key.String(), val) 259 } 260 } 261 } 262 return tval, nil 263} 264 265// Convert given marshal slice to slice of Toml trees 266func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*Tree, error) { 267 tval := make([]*Tree, mval.Len(), mval.Len()) 268 for i := 0; i < mval.Len(); i++ { 269 val, err := e.valueToTree(mtype.Elem(), mval.Index(i)) 270 if err != nil { 271 return nil, err 272 } 273 tval[i] = val 274 } 275 return tval, nil 276} 277 278// Convert given marshal slice to slice of toml values 279func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) { 280 tval := make([]interface{}, mval.Len(), mval.Len()) 281 for i := 0; i < mval.Len(); i++ { 282 val, err := e.valueToToml(mtype.Elem(), mval.Index(i)) 283 if err != nil { 284 return nil, err 285 } 286 tval[i] = val 287 } 288 return tval, nil 289} 290 291// Convert given marshal value to toml value 292func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) { 293 if mtype.Kind() == reflect.Ptr { 294 return e.valueToToml(mtype.Elem(), mval.Elem()) 295 } 296 switch { 297 case isCustomMarshaler(mtype): 298 return callCustomMarshaler(mval) 299 case isTree(mtype): 300 return e.valueToTree(mtype, mval) 301 case isTreeSlice(mtype): 302 return e.valueToTreeSlice(mtype, mval) 303 case isOtherSlice(mtype): 304 return e.valueToOtherSlice(mtype, mval) 305 default: 306 switch mtype.Kind() { 307 case reflect.Bool: 308 return mval.Bool(), nil 309 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 310 return mval.Int(), nil 311 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 312 return mval.Uint(), nil 313 case reflect.Float32, reflect.Float64: 314 return mval.Float(), nil 315 case reflect.String: 316 return mval.String(), nil 317 case reflect.Struct: 318 return mval.Interface().(time.Time), nil 319 default: 320 return nil, fmt.Errorf("Marshal can't handle %v(%v)", mtype, mtype.Kind()) 321 } 322 } 323} 324 325// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v. 326// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for 327// sub-structs, and only definite types can be unmarshaled. 328func (t *Tree) Unmarshal(v interface{}) error { 329 d := Decoder{tval: t} 330 return d.unmarshal(v) 331} 332 333// Marshal returns the TOML encoding of Tree. 334// See Marshal() documentation for types mapping table. 335func (t *Tree) Marshal() ([]byte, error) { 336 var buf bytes.Buffer 337 err := NewEncoder(&buf).Encode(t) 338 return buf.Bytes(), err 339} 340 341// Unmarshal parses the TOML-encoded data and stores the result in the value 342// pointed to by v. Behavior is similar to the Go json encoder, except that there 343// is no concept of an Unmarshaler interface or UnmarshalTOML function for 344// sub-structs, and currently only definite types can be unmarshaled to (i.e. no 345// `interface{}`). 346// 347// The following struct annotations are supported: 348// 349// toml:"Field" Overrides the field's name to map to. 350// 351// See Marshal() documentation for types mapping table. 352func Unmarshal(data []byte, v interface{}) error { 353 t, err := LoadReader(bytes.NewReader(data)) 354 if err != nil { 355 return err 356 } 357 return t.Unmarshal(v) 358} 359 360// Decoder reads and decodes TOML values from an input stream. 361type Decoder struct { 362 r io.Reader 363 tval *Tree 364 encOpts 365} 366 367// NewDecoder returns a new decoder that reads from r. 368func NewDecoder(r io.Reader) *Decoder { 369 return &Decoder{ 370 r: r, 371 encOpts: encOptsDefaults, 372 } 373} 374 375// Decode reads a TOML-encoded value from it's input 376// and unmarshals it in the value pointed at by v. 377// 378// See the documentation for Marshal for details. 379func (d *Decoder) Decode(v interface{}) error { 380 var err error 381 d.tval, err = LoadReader(d.r) 382 if err != nil { 383 return err 384 } 385 return d.unmarshal(v) 386} 387 388func (d *Decoder) unmarshal(v interface{}) error { 389 mtype := reflect.TypeOf(v) 390 if mtype.Kind() != reflect.Ptr || mtype.Elem().Kind() != reflect.Struct { 391 return errors.New("Only a pointer to struct can be unmarshaled from TOML") 392 } 393 394 sval, err := d.valueFromTree(mtype.Elem(), d.tval) 395 if err != nil { 396 return err 397 } 398 reflect.ValueOf(v).Elem().Set(sval) 399 return nil 400} 401 402// Convert toml tree to marshal struct or map, using marshal type 403func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree) (reflect.Value, error) { 404 if mtype.Kind() == reflect.Ptr { 405 return d.unwrapPointer(mtype, tval) 406 } 407 var mval reflect.Value 408 switch mtype.Kind() { 409 case reflect.Struct: 410 mval = reflect.New(mtype).Elem() 411 for i := 0; i < mtype.NumField(); i++ { 412 mtypef := mtype.Field(i) 413 opts := tomlOptions(mtypef) 414 if opts.include { 415 baseKey := opts.name 416 keysToTry := []string{baseKey, strings.ToLower(baseKey), strings.ToTitle(baseKey)} 417 for _, key := range keysToTry { 418 exists := tval.Has(key) 419 if !exists { 420 continue 421 } 422 val := tval.Get(key) 423 mvalf, err := d.valueFromToml(mtypef.Type, val) 424 if err != nil { 425 return mval, formatError(err, tval.GetPosition(key)) 426 } 427 mval.Field(i).Set(mvalf) 428 break 429 } 430 } 431 } 432 case reflect.Map: 433 mval = reflect.MakeMap(mtype) 434 for _, key := range tval.Keys() { 435 // TODO: path splits key 436 val := tval.GetPath([]string{key}) 437 mvalf, err := d.valueFromToml(mtype.Elem(), val) 438 if err != nil { 439 return mval, formatError(err, tval.GetPosition(key)) 440 } 441 mval.SetMapIndex(reflect.ValueOf(key), mvalf) 442 } 443 } 444 return mval, nil 445} 446 447// Convert toml value to marshal struct/map slice, using marshal type 448func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) { 449 mval := reflect.MakeSlice(mtype, len(tval), len(tval)) 450 for i := 0; i < len(tval); i++ { 451 val, err := d.valueFromTree(mtype.Elem(), tval[i]) 452 if err != nil { 453 return mval, err 454 } 455 mval.Index(i).Set(val) 456 } 457 return mval, nil 458} 459 460// Convert toml value to marshal primitive slice, using marshal type 461func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) { 462 mval := reflect.MakeSlice(mtype, len(tval), len(tval)) 463 for i := 0; i < len(tval); i++ { 464 val, err := d.valueFromToml(mtype.Elem(), tval[i]) 465 if err != nil { 466 return mval, err 467 } 468 mval.Index(i).Set(val) 469 } 470 return mval, nil 471} 472 473// Convert toml value to marshal value, using marshal type 474func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}) (reflect.Value, error) { 475 if mtype.Kind() == reflect.Ptr { 476 return d.unwrapPointer(mtype, tval) 477 } 478 479 switch tval.(type) { 480 case *Tree: 481 if isTree(mtype) { 482 return d.valueFromTree(mtype, tval.(*Tree)) 483 } 484 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a tree", tval, tval) 485 case []*Tree: 486 if isTreeSlice(mtype) { 487 return d.valueFromTreeSlice(mtype, tval.([]*Tree)) 488 } 489 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval) 490 case []interface{}: 491 if isOtherSlice(mtype) { 492 return d.valueFromOtherSlice(mtype, tval.([]interface{})) 493 } 494 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval) 495 default: 496 switch mtype.Kind() { 497 case reflect.Bool, reflect.Struct: 498 val := reflect.ValueOf(tval) 499 // if this passes for when mtype is reflect.Struct, tval is a time.Time 500 if !val.Type().ConvertibleTo(mtype) { 501 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) 502 } 503 504 return val.Convert(mtype), nil 505 case reflect.String: 506 val := reflect.ValueOf(tval) 507 // stupidly, int64 is convertible to string. So special case this. 508 if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 { 509 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) 510 } 511 512 return val.Convert(mtype), nil 513 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 514 val := reflect.ValueOf(tval) 515 if !val.Type().ConvertibleTo(mtype) { 516 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) 517 } 518 if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Int()) { 519 return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) 520 } 521 522 return val.Convert(mtype), nil 523 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 524 val := reflect.ValueOf(tval) 525 if !val.Type().ConvertibleTo(mtype) { 526 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) 527 } 528 if val.Int() < 0 { 529 return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String()) 530 } 531 if reflect.Indirect(reflect.New(mtype)).OverflowUint(uint64(val.Int())) { 532 return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) 533 } 534 535 return val.Convert(mtype), nil 536 case reflect.Float32, reflect.Float64: 537 val := reflect.ValueOf(tval) 538 if !val.Type().ConvertibleTo(mtype) { 539 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String()) 540 } 541 if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Float()) { 542 return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String()) 543 } 544 545 return val.Convert(mtype), nil 546 default: 547 return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind()) 548 } 549 } 550} 551 552func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}) (reflect.Value, error) { 553 val, err := d.valueFromToml(mtype.Elem(), tval) 554 if err != nil { 555 return reflect.ValueOf(nil), err 556 } 557 mval := reflect.New(mtype.Elem()) 558 mval.Elem().Set(val) 559 return mval, nil 560} 561 562func tomlOptions(vf reflect.StructField) tomlOpts { 563 tag := vf.Tag.Get("toml") 564 parse := strings.Split(tag, ",") 565 var comment string 566 if c := vf.Tag.Get("comment"); c != "" { 567 comment = c 568 } 569 commented, _ := strconv.ParseBool(vf.Tag.Get("commented")) 570 multiline, _ := strconv.ParseBool(vf.Tag.Get(tagKeyMultiline)) 571 result := tomlOpts{name: vf.Name, comment: comment, commented: commented, multiline: multiline, include: true, omitempty: false} 572 if parse[0] != "" { 573 if parse[0] == "-" && len(parse) == 1 { 574 result.include = false 575 } else { 576 result.name = strings.Trim(parse[0], " ") 577 } 578 } 579 if vf.PkgPath != "" { 580 result.include = false 581 } 582 if len(parse) > 1 && strings.Trim(parse[1], " ") == "omitempty" { 583 result.omitempty = true 584 } 585 if vf.Type.Kind() == reflect.Ptr { 586 result.omitempty = true 587 } 588 return result 589} 590 591func isZero(val reflect.Value) bool { 592 switch val.Type().Kind() { 593 case reflect.Map: 594 fallthrough 595 case reflect.Array: 596 fallthrough 597 case reflect.Slice: 598 return val.Len() == 0 599 default: 600 return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface()) 601 } 602} 603 604func formatError(err error, pos Position) error { 605 if err.Error()[0] == '(' { // Error already contains position information 606 return err 607 } 608 return fmt.Errorf("%s: %s", pos, err) 609} 610