--- /dev/null
+package toml
+
+import (
+ "fmt"
+ "log"
+ "math"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func TestDecodeSimple(t *testing.T) {
+ var testSimple = `
+age = 250
+andrew = "gallant"
+kait = "brady"
+now = 1987-07-05T05:45:00Z
+nowEast = 2017-06-22T16:15:21+08:00
+nowWest = 2017-06-22T02:14:36-06:00
+yesOrNo = true
+pi = 3.14
+colors = [
+ ["red", "green", "blue"],
+ ["cyan", "magenta", "yellow", "black"],
+]
+
+[My.Cats]
+plato = "cat 1"
+cauchy = "cat 2"
+`
+
+ type cats struct {
+ Plato string
+ Cauchy string
+ }
+ type simple struct {
+ Age int
+ Colors [][]string
+ Pi float64
+ YesOrNo bool
+ Now time.Time
+ NowEast time.Time
+ NowWest time.Time
+ Andrew string
+ Kait string
+ My map[string]cats
+ }
+
+ var val simple
+ _, err := Decode(testSimple, &val)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00")
+ if err != nil {
+ panic(err)
+ }
+ nowEast, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-06-22T16:15:21+08:00")
+ if err != nil {
+ panic(err)
+ }
+ nowWest, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-06-22T02:14:36-06:00")
+ if err != nil {
+ panic(err)
+ }
+ var answer = simple{
+ Age: 250,
+ Andrew: "gallant",
+ Kait: "brady",
+ Now: now,
+ NowEast: nowEast,
+ NowWest: nowWest,
+ YesOrNo: true,
+ Pi: 3.14,
+ Colors: [][]string{
+ {"red", "green", "blue"},
+ {"cyan", "magenta", "yellow", "black"},
+ },
+ My: map[string]cats{
+ "Cats": {Plato: "cat 1", Cauchy: "cat 2"},
+ },
+ }
+ if !reflect.DeepEqual(val, answer) {
+ t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n",
+ answer, val)
+ }
+}
+
+func TestDecodeEmbedded(t *testing.T) {
+ type Dog struct{ Name string }
+ type Age int
+ type cat struct{ Name string }
+
+ for _, test := range []struct {
+ label string
+ input string
+ decodeInto interface{}
+ wantDecoded interface{}
+ }{
+ {
+ label: "embedded struct",
+ input: `Name = "milton"`,
+ decodeInto: &struct{ Dog }{},
+ wantDecoded: &struct{ Dog }{Dog{"milton"}},
+ },
+ {
+ label: "embedded non-nil pointer to struct",
+ input: `Name = "milton"`,
+ decodeInto: &struct{ *Dog }{},
+ wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
+ },
+ {
+ label: "embedded nil pointer to struct",
+ input: ``,
+ decodeInto: &struct{ *Dog }{},
+ wantDecoded: &struct{ *Dog }{nil},
+ },
+ {
+ label: "unexported embedded struct",
+ input: `Name = "socks"`,
+ decodeInto: &struct{ cat }{},
+ wantDecoded: &struct{ cat }{cat{"socks"}},
+ },
+ {
+ label: "embedded int",
+ input: `Age = -5`,
+ decodeInto: &struct{ Age }{},
+ wantDecoded: &struct{ Age }{-5},
+ },
+ } {
+ _, err := Decode(test.input, test.decodeInto)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) {
+ t.Errorf("%s: want decoded == %+v, got %+v",
+ test.label, test.wantDecoded, test.decodeInto)
+ }
+ }
+}
+
+func TestDecodeIgnoredFields(t *testing.T) {
+ type simple struct {
+ Number int `toml:"-"`
+ }
+ const input = `
+Number = 123
+- = 234
+`
+ var s simple
+ if _, err := Decode(input, &s); err != nil {
+ t.Fatal(err)
+ }
+ if s.Number != 0 {
+ t.Errorf("got: %d; want 0", s.Number)
+ }
+}
+
+func TestTableArrays(t *testing.T) {
+ var tomlTableArrays = `
+[[albums]]
+name = "Born to Run"
+
+ [[albums.songs]]
+ name = "Jungleland"
+
+ [[albums.songs]]
+ name = "Meeting Across the River"
+
+[[albums]]
+name = "Born in the USA"
+
+ [[albums.songs]]
+ name = "Glory Days"
+
+ [[albums.songs]]
+ name = "Dancing in the Dark"
+`
+
+ type Song struct {
+ Name string
+ }
+
+ type Album struct {
+ Name string
+ Songs []Song
+ }
+
+ type Music struct {
+ Albums []Album
+ }
+
+ expected := Music{[]Album{
+ {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
+ {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
+ }}
+ var got Music
+ if _, err := Decode(tomlTableArrays, &got); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(expected, got) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
+ }
+}
+
+func TestTableNesting(t *testing.T) {
+ for _, tt := range []struct {
+ t string
+ want []string
+ }{
+ {"[a.b.c]", []string{"a", "b", "c"}},
+ {`[a."b.c"]`, []string{"a", "b.c"}},
+ {`[a.'b.c']`, []string{"a", "b.c"}},
+ {`[a.' b ']`, []string{"a", " b "}},
+ {"[ d.e.f ]", []string{"d", "e", "f"}},
+ {"[ g . h . i ]", []string{"g", "h", "i"}},
+ {`[ j . "ʞ" . 'l' ]`, []string{"j", "ʞ", "l"}},
+ } {
+ var m map[string]interface{}
+ if _, err := Decode(tt.t, &m); err != nil {
+ t.Errorf("Decode(%q): got error: %s", tt.t, err)
+ continue
+ }
+ if keys := extractNestedKeys(m); !reflect.DeepEqual(keys, tt.want) {
+ t.Errorf("Decode(%q): got nested keys %#v; want %#v",
+ tt.t, keys, tt.want)
+ }
+ }
+}
+
+func extractNestedKeys(v map[string]interface{}) []string {
+ var result []string
+ for {
+ if len(v) != 1 {
+ return result
+ }
+ for k, m := range v {
+ result = append(result, k)
+ var ok bool
+ v, ok = m.(map[string]interface{})
+ if !ok {
+ return result
+ }
+ }
+
+ }
+}
+
+// Case insensitive matching tests.
+// A bit more comprehensive than needed given the current implementation,
+// but implementations change.
+// Probably still missing demonstrations of some ugly corner cases regarding
+// case insensitive matching and multiple fields.
+func TestCase(t *testing.T) {
+ var caseToml = `
+tOpString = "string"
+tOpInt = 1
+tOpFloat = 1.1
+tOpBool = true
+tOpdate = 2006-01-02T15:04:05Z
+tOparray = [ "array" ]
+Match = "i should be in Match only"
+MatcH = "i should be in MatcH only"
+once = "just once"
+[nEst.eD]
+nEstedString = "another string"
+`
+
+ type InsensitiveEd struct {
+ NestedString string
+ }
+
+ type InsensitiveNest struct {
+ Ed InsensitiveEd
+ }
+
+ type Insensitive struct {
+ TopString string
+ TopInt int
+ TopFloat float64
+ TopBool bool
+ TopDate time.Time
+ TopArray []string
+ Match string
+ MatcH string
+ Once string
+ OncE string
+ Nest InsensitiveNest
+ }
+
+ tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5])
+ if err != nil {
+ panic(err)
+ }
+ expected := Insensitive{
+ TopString: "string",
+ TopInt: 1,
+ TopFloat: 1.1,
+ TopBool: true,
+ TopDate: tme,
+ TopArray: []string{"array"},
+ MatcH: "i should be in MatcH only",
+ Match: "i should be in Match only",
+ Once: "just once",
+ OncE: "",
+ Nest: InsensitiveNest{
+ Ed: InsensitiveEd{NestedString: "another string"},
+ },
+ }
+ var got Insensitive
+ if _, err := Decode(caseToml, &got); err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(expected, got) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
+ }
+}
+
+func TestPointers(t *testing.T) {
+ type Object struct {
+ Type string
+ Description string
+ }
+
+ type Dict struct {
+ NamedObject map[string]*Object
+ BaseObject *Object
+ Strptr *string
+ Strptrs []*string
+ }
+ s1, s2, s3 := "blah", "abc", "def"
+ expected := &Dict{
+ Strptr: &s1,
+ Strptrs: []*string{&s2, &s3},
+ NamedObject: map[string]*Object{
+ "foo": {"FOO", "fooooo!!!"},
+ "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
+ },
+ BaseObject: &Object{"BASE", "da base"},
+ }
+
+ ex1 := `
+Strptr = "blah"
+Strptrs = ["abc", "def"]
+
+[NamedObject.foo]
+Type = "FOO"
+Description = "fooooo!!!"
+
+[NamedObject.bar]
+Type = "BAR"
+Description = "ba-ba-ba-ba-barrrr!!!"
+
+[BaseObject]
+Type = "BASE"
+Description = "da base"
+`
+ dict := new(Dict)
+ _, err := Decode(ex1, dict)
+ if err != nil {
+ t.Errorf("Decode error: %v", err)
+ }
+ if !reflect.DeepEqual(expected, dict) {
+ t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
+ }
+}
+
+func TestDecodeDatetime(t *testing.T) {
+ const noTimestamp = "2006-01-02T15:04:05"
+ for _, tt := range []struct {
+ s string
+ t string
+ format string
+ }{
+ {"1979-05-27T07:32:00Z", "1979-05-27T07:32:00Z", time.RFC3339},
+ {"1979-05-27T00:32:00-07:00", "1979-05-27T00:32:00-07:00", time.RFC3339},
+ {
+ "1979-05-27T00:32:00.999999-07:00",
+ "1979-05-27T00:32:00.999999-07:00",
+ time.RFC3339,
+ },
+ {"1979-05-27T07:32:00", "1979-05-27T07:32:00", noTimestamp},
+ {
+ "1979-05-27T00:32:00.999999",
+ "1979-05-27T00:32:00.999999",
+ noTimestamp,
+ },
+ {"1979-05-27", "1979-05-27T00:00:00", noTimestamp},
+ } {
+ var x struct{ D time.Time }
+ input := "d = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ want, err := time.ParseInLocation(tt.format, tt.t, time.Local)
+ if err != nil {
+ panic(err)
+ }
+ if !x.D.Equal(want) {
+ t.Errorf("Decode(%q): got %s; want %s", input, x.D, want)
+ }
+ }
+}
+
+func TestDecodeBadDatetime(t *testing.T) {
+ var x struct{ T time.Time }
+ for _, s := range []string{
+ "123",
+ "2006-01-50T00:00:00Z",
+ "2006-01-30T00:00",
+ "2006-01-30T",
+ } {
+ input := "T = " + s
+ if _, err := Decode(input, &x); err == nil {
+ t.Errorf("Expected invalid DateTime error for %q", s)
+ }
+ }
+}
+
+func TestDecodeMultilineStrings(t *testing.T) {
+ var x struct {
+ S string
+ }
+ const s0 = `s = """
+a b \n c
+d e f
+"""`
+ if _, err := Decode(s0, &x); err != nil {
+ t.Fatal(err)
+ }
+ if want := "a b \n c\nd e f\n"; x.S != want {
+ t.Errorf("got: %q; want: %q", x.S, want)
+ }
+ const s1 = `s = """a b c\
+"""`
+ if _, err := Decode(s1, &x); err != nil {
+ t.Fatal(err)
+ }
+ if want := "a b c"; x.S != want {
+ t.Errorf("got: %q; want: %q", x.S, want)
+ }
+}
+
+type sphere struct {
+ Center [3]float64
+ Radius float64
+}
+
+func TestDecodeSimpleArray(t *testing.T) {
+ var s1 sphere
+ if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestDecodeArrayWrongSize(t *testing.T) {
+ var s1 sphere
+ if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil {
+ t.Fatal("Expected array type mismatch error")
+ }
+}
+
+func TestDecodeLargeIntoSmallInt(t *testing.T) {
+ type table struct {
+ Value int8
+ }
+ var tab table
+ if _, err := Decode(`value = 500`, &tab); err == nil {
+ t.Fatal("Expected integer out-of-bounds error.")
+ }
+}
+
+func TestDecodeSizedInts(t *testing.T) {
+ type table struct {
+ U8 uint8
+ U16 uint16
+ U32 uint32
+ U64 uint64
+ U uint
+ I8 int8
+ I16 int16
+ I32 int32
+ I64 int64
+ I int
+ }
+ answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1}
+ toml := `
+ u8 = 1
+ u16 = 1
+ u32 = 1
+ u64 = 1
+ u = 1
+ i8 = -1
+ i16 = -1
+ i32 = -1
+ i64 = -1
+ i = -1
+ `
+ var tab table
+ if _, err := Decode(toml, &tab); err != nil {
+ t.Fatal(err.Error())
+ }
+ if answer != tab {
+ t.Fatalf("Expected %#v but got %#v", answer, tab)
+ }
+}
+
+func TestDecodeInts(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want int64
+ }{
+ {"0", 0},
+ {"+99", 99},
+ {"-10", -10},
+ {"1_234_567", 1234567},
+ {"1_2_3_4", 1234},
+ {"-9_223_372_036_854_775_808", math.MinInt64},
+ {"9_223_372_036_854_775_807", math.MaxInt64},
+ } {
+ var x struct{ N int64 }
+ input := "n = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ if x.N != tt.want {
+ t.Errorf("Decode(%q): got %d; want %d", input, x.N, tt.want)
+ }
+ }
+}
+
+func TestDecodeFloats(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want float64
+ }{
+ {"+1.0", 1},
+ {"3.1415", 3.1415},
+ {"-0.01", -0.01},
+ {"5e+22", 5e22},
+ {"1e6", 1e6},
+ {"-2E-2", -2e-2},
+ {"6.626e-34", 6.626e-34},
+ {"9_224_617.445_991_228_313", 9224617.445991228313},
+ {"9_876.54_32e1_0", 9876.5432e10},
+ } {
+ var x struct{ N float64 }
+ input := "n = " + tt.s
+ if _, err := Decode(input, &x); err != nil {
+ t.Errorf("Decode(%q): got error: %s", input, err)
+ continue
+ }
+ if x.N != tt.want {
+ t.Errorf("Decode(%q): got %f; want %f", input, x.N, tt.want)
+ }
+ }
+}
+
+func TestDecodeMalformedNumbers(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want string
+ }{
+ {"++99", "expected a digit"},
+ {"0..1", "must be followed by one or more digits"},
+ {"0.1.2", "Invalid float value"},
+ {"1e2.3", "Invalid float value"},
+ {"1e2e3", "Invalid float value"},
+ {"_123", "expected value"},
+ {"123_", "surrounded by digits"},
+ {"1._23", "surrounded by digits"},
+ {"1e__23", "surrounded by digits"},
+ {"123.", "must be followed by one or more digits"},
+ {"1.e2", "must be followed by one or more digits"},
+ } {
+ var x struct{ N interface{} }
+ input := "n = " + tt.s
+ _, err := Decode(input, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil, want error containing %q",
+ input, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%q): got %q, want error containing %q",
+ input, err, tt.want)
+ }
+ }
+}
+
+func TestDecodeBadValues(t *testing.T) {
+ for _, tt := range []struct {
+ v interface{}
+ want string
+ }{
+ {3, "non-pointer int"},
+ {(*int)(nil), "nil"},
+ } {
+ _, err := Decode(`x = 3`, tt.v)
+ if err == nil {
+ t.Errorf("Decode(%v): got nil; want error containing %q",
+ tt.v, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%v): got %q; want error containing %q",
+ tt.v, err, tt.want)
+ }
+ }
+}
+
+func TestUnmarshaler(t *testing.T) {
+
+ var tomlBlob = `
+[dishes.hamboogie]
+name = "Hamboogie with fries"
+price = 10.99
+
+[[dishes.hamboogie.ingredients]]
+name = "Bread Bun"
+
+[[dishes.hamboogie.ingredients]]
+name = "Lettuce"
+
+[[dishes.hamboogie.ingredients]]
+name = "Real Beef Patty"
+
+[[dishes.hamboogie.ingredients]]
+name = "Tomato"
+
+[dishes.eggsalad]
+name = "Egg Salad with rice"
+price = 3.99
+
+[[dishes.eggsalad.ingredients]]
+name = "Egg"
+
+[[dishes.eggsalad.ingredients]]
+name = "Mayo"
+
+[[dishes.eggsalad.ingredients]]
+name = "Rice"
+`
+ m := &menu{}
+ if _, err := Decode(tomlBlob, m); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(m.Dishes) != 2 {
+ t.Log("two dishes should be loaded with UnmarshalTOML()")
+ t.Errorf("expected %d but got %d", 2, len(m.Dishes))
+ }
+
+ eggSalad := m.Dishes["eggsalad"]
+ if _, ok := interface{}(eggSalad).(dish); !ok {
+ t.Errorf("expected a dish")
+ }
+
+ if eggSalad.Name != "Egg Salad with rice" {
+ t.Errorf("expected the dish to be named 'Egg Salad with rice'")
+ }
+
+ if len(eggSalad.Ingredients) != 3 {
+ t.Log("dish should be loaded with UnmarshalTOML()")
+ t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients))
+ }
+
+ found := false
+ for _, i := range eggSalad.Ingredients {
+ if i.Name == "Rice" {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Error("Rice was not loaded in UnmarshalTOML()")
+ }
+
+ // test on a value - must be passed as *
+ o := menu{}
+ if _, err := Decode(tomlBlob, &o); err != nil {
+ t.Fatal(err)
+ }
+
+}
+
+func TestDecodeInlineTable(t *testing.T) {
+ input := `
+[CookieJar]
+Types = {Chocolate = "yummy", Oatmeal = "best ever"}
+
+[Seasons]
+Locations = {NY = {Temp = "not cold", Rating = 4}, MI = {Temp = "freezing", Rating = 9}}
+`
+ type cookieJar struct {
+ Types map[string]string
+ }
+ type properties struct {
+ Temp string
+ Rating int
+ }
+ type seasons struct {
+ Locations map[string]properties
+ }
+ type wrapper struct {
+ CookieJar cookieJar
+ Seasons seasons
+ }
+ var got wrapper
+
+ meta, err := Decode(input, &got)
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := wrapper{
+ CookieJar: cookieJar{
+ Types: map[string]string{
+ "Chocolate": "yummy",
+ "Oatmeal": "best ever",
+ },
+ },
+ Seasons: seasons{
+ Locations: map[string]properties{
+ "NY": {
+ Temp: "not cold",
+ Rating: 4,
+ },
+ "MI": {
+ Temp: "freezing",
+ Rating: 9,
+ },
+ },
+ },
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("after decode, got:\n\n%#v\n\nwant:\n\n%#v", got, want)
+ }
+ if len(meta.keys) != 12 {
+ t.Errorf("after decode, got %d meta keys; want 12", len(meta.keys))
+ }
+ if len(meta.types) != 12 {
+ t.Errorf("after decode, got %d meta types; want 12", len(meta.types))
+ }
+}
+
+func TestDecodeInlineTableArray(t *testing.T) {
+ type point struct {
+ X, Y, Z int
+ }
+ var got struct {
+ Points []point
+ }
+ // Example inline table array from the spec.
+ const in = `
+points = [ { x = 1, y = 2, z = 3 },
+ { x = 7, y = 8, z = 9 },
+ { x = 2, y = 4, z = 8 } ]
+
+`
+ if _, err := Decode(in, &got); err != nil {
+ t.Fatal(err)
+ }
+ want := []point{
+ {X: 1, Y: 2, Z: 3},
+ {X: 7, Y: 8, Z: 9},
+ {X: 2, Y: 4, Z: 8},
+ }
+ if !reflect.DeepEqual(got.Points, want) {
+ t.Errorf("got %#v; want %#v", got.Points, want)
+ }
+}
+
+func TestDecodeMalformedInlineTable(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ want string
+ }{
+ {"{,}", "unexpected comma"},
+ {"{x = 3 y = 4}", "expected a comma or an inline table terminator"},
+ {"{x=3,,y=4}", "unexpected comma"},
+ {"{x=3,\ny=4}", "newlines not allowed"},
+ {"{x=3\n,y=4}", "newlines not allowed"},
+ } {
+ var x struct{ A map[string]int }
+ input := "a = " + tt.s
+ _, err := Decode(input, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil, want error containing %q",
+ input, tt.want)
+ continue
+ }
+ if !strings.Contains(err.Error(), tt.want) {
+ t.Errorf("Decode(%q): got %q, want error containing %q",
+ input, err, tt.want)
+ }
+ }
+}
+
+type menu struct {
+ Dishes map[string]dish
+}
+
+func (m *menu) UnmarshalTOML(p interface{}) error {
+ m.Dishes = make(map[string]dish)
+ data, _ := p.(map[string]interface{})
+ dishes := data["dishes"].(map[string]interface{})
+ for n, v := range dishes {
+ if d, ok := v.(map[string]interface{}); ok {
+ nd := dish{}
+ nd.UnmarshalTOML(d)
+ m.Dishes[n] = nd
+ } else {
+ return fmt.Errorf("not a dish")
+ }
+ }
+ return nil
+}
+
+type dish struct {
+ Name string
+ Price float32
+ Ingredients []ingredient
+}
+
+func (d *dish) UnmarshalTOML(p interface{}) error {
+ data, _ := p.(map[string]interface{})
+ d.Name, _ = data["name"].(string)
+ d.Price, _ = data["price"].(float32)
+ ingredients, _ := data["ingredients"].([]map[string]interface{})
+ for _, e := range ingredients {
+ n, _ := interface{}(e).(map[string]interface{})
+ name, _ := n["name"].(string)
+ i := ingredient{name}
+ d.Ingredients = append(d.Ingredients, i)
+ }
+ return nil
+}
+
+type ingredient struct {
+ Name string
+}
+
+func TestDecodeSlices(t *testing.T) {
+ type T struct {
+ S []string
+ }
+ for i, tt := range []struct {
+ v T
+ input string
+ want T
+ }{
+ {T{}, "", T{}},
+ {T{[]string{}}, "", T{[]string{}}},
+ {T{[]string{"a", "b"}}, "", T{[]string{"a", "b"}}},
+ {T{}, "S = []", T{[]string{}}},
+ {T{[]string{}}, "S = []", T{[]string{}}},
+ {T{[]string{"a", "b"}}, "S = []", T{[]string{}}},
+ {T{}, `S = ["x"]`, T{[]string{"x"}}},
+ {T{[]string{}}, `S = ["x"]`, T{[]string{"x"}}},
+ {T{[]string{"a", "b"}}, `S = ["x"]`, T{[]string{"x"}}},
+ } {
+ if _, err := Decode(tt.input, &tt.v); err != nil {
+ t.Errorf("[%d] %s", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.v, tt.want) {
+ t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
+ }
+ }
+}
+
+func TestDecodePrimitive(t *testing.T) {
+ type S struct {
+ P Primitive
+ }
+ type T struct {
+ S []int
+ }
+ slicep := func(s []int) *[]int { return &s }
+ arrayp := func(a [2]int) *[2]int { return &a }
+ mapp := func(m map[string]int) *map[string]int { return &m }
+ for i, tt := range []struct {
+ v interface{}
+ input string
+ want interface{}
+ }{
+ // slices
+ {slicep(nil), "", slicep(nil)},
+ {slicep([]int{}), "", slicep([]int{})},
+ {slicep([]int{1, 2, 3}), "", slicep([]int{1, 2, 3})},
+ {slicep(nil), "P = [1,2]", slicep([]int{1, 2})},
+ {slicep([]int{}), "P = [1,2]", slicep([]int{1, 2})},
+ {slicep([]int{1, 2, 3}), "P = [1,2]", slicep([]int{1, 2})},
+
+ // arrays
+ {arrayp([2]int{2, 3}), "", arrayp([2]int{2, 3})},
+ {arrayp([2]int{2, 3}), "P = [3,4]", arrayp([2]int{3, 4})},
+
+ // maps
+ {mapp(nil), "", mapp(nil)},
+ {mapp(map[string]int{}), "", mapp(map[string]int{})},
+ {mapp(map[string]int{"a": 1}), "", mapp(map[string]int{"a": 1})},
+ {mapp(nil), "[P]\na = 2", mapp(map[string]int{"a": 2})},
+ {mapp(map[string]int{}), "[P]\na = 2", mapp(map[string]int{"a": 2})},
+ {mapp(map[string]int{"a": 1, "b": 3}), "[P]\na = 2", mapp(map[string]int{"a": 2, "b": 3})},
+
+ // structs
+ {&T{nil}, "[P]", &T{nil}},
+ {&T{[]int{}}, "[P]", &T{[]int{}}},
+ {&T{[]int{1, 2, 3}}, "[P]", &T{[]int{1, 2, 3}}},
+ {&T{nil}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ {&T{[]int{}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ {&T{[]int{1, 2, 3}}, "[P]\nS = [1,2]", &T{[]int{1, 2}}},
+ } {
+ var s S
+ md, err := Decode(tt.input, &s)
+ if err != nil {
+ t.Errorf("[%d] Decode error: %s", i, err)
+ continue
+ }
+ if err := md.PrimitiveDecode(s.P, tt.v); err != nil {
+ t.Errorf("[%d] PrimitiveDecode error: %s", i, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.v, tt.want) {
+ t.Errorf("[%d] got %#v; want %#v", i, tt.v, tt.want)
+ }
+ }
+}
+
+func TestDecodeErrors(t *testing.T) {
+ for _, s := range []string{
+ `x="`,
+ `x='`,
+ `x='''`,
+
+ // Cases found by fuzzing in
+ // https://github.com/BurntSushi/toml/issues/155.
+ `""�`, // used to panic with index out of range
+ `e="""`, // used to hang
+ } {
+ var x struct{}
+ _, err := Decode(s, &x)
+ if err == nil {
+ t.Errorf("Decode(%q): got nil error", s)
+ }
+ }
+}
+
+// Test for https://github.com/BurntSushi/toml/pull/166.
+func TestDecodeBoolArray(t *testing.T) {
+ for _, tt := range []struct {
+ s string
+ got interface{}
+ want interface{}
+ }{
+ {
+ "a = [true, false]",
+ &struct{ A []bool }{},
+ &struct{ A []bool }{[]bool{true, false}},
+ },
+ {
+ "a = {a = true, b = false}",
+ &struct{ A map[string]bool }{},
+ &struct{ A map[string]bool }{map[string]bool{"a": true, "b": false}},
+ },
+ } {
+ if _, err := Decode(tt.s, tt.got); err != nil {
+ t.Errorf("Decode(%q): %s", tt.s, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.got, tt.want) {
+ t.Errorf("Decode(%q): got %#v; want %#v", tt.s, tt.got, tt.want)
+ }
+ }
+}
+
+func ExampleMetaData_PrimitiveDecode() {
+ var md MetaData
+ var err error
+
+ var tomlBlob = `
+ranking = ["Springsteen", "J Geils"]
+
+[bands.Springsteen]
+started = 1973
+albums = ["Greetings", "WIESS", "Born to Run", "Darkness"]
+
+[bands."J Geils"]
+started = 1970
+albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"]
+`
+
+ type band struct {
+ Started int
+ Albums []string
+ }
+ type classics struct {
+ Ranking []string
+ Bands map[string]Primitive
+ }
+
+ // Do the initial decode. Reflection is delayed on Primitive values.
+ var music classics
+ if md, err = Decode(tomlBlob, &music); err != nil {
+ log.Fatal(err)
+ }
+
+ // MetaData still includes information on Primitive values.
+ fmt.Printf("Is `bands.Springsteen` defined? %v\n",
+ md.IsDefined("bands", "Springsteen"))
+
+ // Decode primitive data into Go values.
+ for _, artist := range music.Ranking {
+ // A band is a primitive value, so we need to decode it to get a
+ // real `band` value.
+ primValue := music.Bands[artist]
+
+ var aBand band
+ if err = md.PrimitiveDecode(primValue, &aBand); err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s started in %d.\n", artist, aBand.Started)
+ }
+ // Check to see if there were any fields left undecoded.
+ // Note that this won't be empty before decoding the Primitive value!
+ fmt.Printf("Undecoded: %q\n", md.Undecoded())
+
+ // Output:
+ // Is `bands.Springsteen` defined? true
+ // Springsteen started in 1973.
+ // J Geils started in 1970.
+ // Undecoded: []
+}
+
+func ExampleDecode() {
+ var tomlBlob = `
+# Some comments.
+[alpha]
+ip = "10.0.0.1"
+
+ [alpha.config]
+ Ports = [ 8001, 8002 ]
+ Location = "Toronto"
+ Created = 1987-07-05T05:45:00Z
+
+[beta]
+ip = "10.0.0.2"
+
+ [beta.config]
+ Ports = [ 9001, 9002 ]
+ Location = "New Jersey"
+ Created = 1887-01-05T05:55:00Z
+`
+
+ type serverConfig struct {
+ Ports []int
+ Location string
+ Created time.Time
+ }
+
+ type server struct {
+ IP string `toml:"ip,omitempty"`
+ Config serverConfig `toml:"config"`
+ }
+
+ type servers map[string]server
+
+ var config servers
+ if _, err := Decode(tomlBlob, &config); err != nil {
+ log.Fatal(err)
+ }
+
+ for _, name := range []string{"alpha", "beta"} {
+ s := config[name]
+ fmt.Printf("Server: %s (ip: %s) in %s created on %s\n",
+ name, s.IP, s.Config.Location,
+ s.Config.Created.Format("2006-01-02"))
+ fmt.Printf("Ports: %v\n", s.Config.Ports)
+ }
+
+ // Output:
+ // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05
+ // Ports: [8001 8002]
+ // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05
+ // Ports: [9001 9002]
+}
+
+type duration struct {
+ time.Duration
+}
+
+func (d *duration) UnmarshalText(text []byte) error {
+ var err error
+ d.Duration, err = time.ParseDuration(string(text))
+ return err
+}
+
+// Example Unmarshaler shows how to decode TOML strings into your own
+// custom data type.
+func Example_unmarshaler() {
+ blob := `
+[[song]]
+name = "Thunder Road"
+duration = "4m49s"
+
+[[song]]
+name = "Stairway to Heaven"
+duration = "8m03s"
+`
+ type song struct {
+ Name string
+ Duration duration
+ }
+ type songs struct {
+ Song []song
+ }
+ var favorites songs
+ if _, err := Decode(blob, &favorites); err != nil {
+ log.Fatal(err)
+ }
+
+ // Code to implement the TextUnmarshaler interface for `duration`:
+ //
+ // type duration struct {
+ // time.Duration
+ // }
+ //
+ // func (d *duration) UnmarshalText(text []byte) error {
+ // var err error
+ // d.Duration, err = time.ParseDuration(string(text))
+ // return err
+ // }
+
+ for _, s := range favorites.Song {
+ fmt.Printf("%s (%s)\n", s.Name, s.Duration)
+ }
+ // Output:
+ // Thunder Road (4m49s)
+ // Stairway to Heaven (8m3s)
+}
+
+// Example StrictDecoding shows how to detect whether there are keys in the
+// TOML document that weren't decoded into the value given. This is useful
+// for returning an error to the user if they've included extraneous fields
+// in their configuration.
+func Example_strictDecoding() {
+ var blob = `
+key1 = "value1"
+key2 = "value2"
+key3 = "value3"
+`
+ type config struct {
+ Key1 string
+ Key3 string
+ }
+
+ var conf config
+ md, err := Decode(blob, &conf)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("Undecoded keys: %q\n", md.Undecoded())
+ // Output:
+ // Undecoded keys: ["key2"]
+}
+
+// Example UnmarshalTOML shows how to implement a struct type that knows how to
+// unmarshal itself. The struct must take full responsibility for mapping the
+// values passed into the struct. The method may be used with interfaces in a
+// struct in cases where the actual type is not known until the data is
+// examined.
+func Example_unmarshalTOML() {
+
+ var blob = `
+[[parts]]
+type = "valve"
+id = "valve-1"
+size = 1.2
+rating = 4
+
+[[parts]]
+type = "valve"
+id = "valve-2"
+size = 2.1
+rating = 5
+
+[[parts]]
+type = "pipe"
+id = "pipe-1"
+length = 2.1
+diameter = 12
+
+[[parts]]
+type = "cable"
+id = "cable-1"
+length = 12
+rating = 3.1
+`
+ o := &order{}
+ err := Unmarshal([]byte(blob), o)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Println(len(o.parts))
+
+ for _, part := range o.parts {
+ fmt.Println(part.Name())
+ }
+
+ // Code to implement UmarshalJSON.
+
+ // type order struct {
+ // // NOTE `order.parts` is a private slice of type `part` which is an
+ // // interface and may only be loaded from toml using the
+ // // UnmarshalTOML() method of the Umarshaler interface.
+ // parts parts
+ // }
+
+ // func (o *order) UnmarshalTOML(data interface{}) error {
+
+ // // NOTE the example below contains detailed type casting to show how
+ // // the 'data' is retrieved. In operational use, a type cast wrapper
+ // // may be preferred e.g.
+ // //
+ // // func AsMap(v interface{}) (map[string]interface{}, error) {
+ // // return v.(map[string]interface{})
+ // // }
+ // //
+ // // resulting in:
+ // // d, _ := AsMap(data)
+ // //
+
+ // d, _ := data.(map[string]interface{})
+ // parts, _ := d["parts"].([]map[string]interface{})
+
+ // for _, p := range parts {
+
+ // typ, _ := p["type"].(string)
+ // id, _ := p["id"].(string)
+
+ // // detect the type of part and handle each case
+ // switch p["type"] {
+ // case "valve":
+
+ // size := float32(p["size"].(float64))
+ // rating := int(p["rating"].(int64))
+
+ // valve := &valve{
+ // Type: typ,
+ // ID: id,
+ // Size: size,
+ // Rating: rating,
+ // }
+
+ // o.parts = append(o.parts, valve)
+
+ // case "pipe":
+
+ // length := float32(p["length"].(float64))
+ // diameter := int(p["diameter"].(int64))
+
+ // pipe := &pipe{
+ // Type: typ,
+ // ID: id,
+ // Length: length,
+ // Diameter: diameter,
+ // }
+
+ // o.parts = append(o.parts, pipe)
+
+ // case "cable":
+
+ // length := int(p["length"].(int64))
+ // rating := float32(p["rating"].(float64))
+
+ // cable := &cable{
+ // Type: typ,
+ // ID: id,
+ // Length: length,
+ // Rating: rating,
+ // }
+
+ // o.parts = append(o.parts, cable)
+
+ // }
+ // }
+
+ // return nil
+ // }
+
+ // type parts []part
+
+ // type part interface {
+ // Name() string
+ // }
+
+ // type valve struct {
+ // Type string
+ // ID string
+ // Size float32
+ // Rating int
+ // }
+
+ // func (v *valve) Name() string {
+ // return fmt.Sprintf("VALVE: %s", v.ID)
+ // }
+
+ // type pipe struct {
+ // Type string
+ // ID string
+ // Length float32
+ // Diameter int
+ // }
+
+ // func (p *pipe) Name() string {
+ // return fmt.Sprintf("PIPE: %s", p.ID)
+ // }
+
+ // type cable struct {
+ // Type string
+ // ID string
+ // Length int
+ // Rating float32
+ // }
+
+ // func (c *cable) Name() string {
+ // return fmt.Sprintf("CABLE: %s", c.ID)
+ // }
+
+ // Output:
+ // 4
+ // VALVE: valve-1
+ // VALVE: valve-2
+ // PIPE: pipe-1
+ // CABLE: cable-1
+
+}
+
+type order struct {
+ // NOTE `order.parts` is a private slice of type `part` which is an
+ // interface and may only be loaded from toml using the UnmarshalTOML()
+ // method of the Umarshaler interface.
+ parts parts
+}
+
+func (o *order) UnmarshalTOML(data interface{}) error {
+
+ // NOTE the example below contains detailed type casting to show how
+ // the 'data' is retrieved. In operational use, a type cast wrapper
+ // may be preferred e.g.
+ //
+ // func AsMap(v interface{}) (map[string]interface{}, error) {
+ // return v.(map[string]interface{})
+ // }
+ //
+ // resulting in:
+ // d, _ := AsMap(data)
+ //
+
+ d, _ := data.(map[string]interface{})
+ parts, _ := d["parts"].([]map[string]interface{})
+
+ for _, p := range parts {
+
+ typ, _ := p["type"].(string)
+ id, _ := p["id"].(string)
+
+ // detect the type of part and handle each case
+ switch p["type"] {
+ case "valve":
+
+ size := float32(p["size"].(float64))
+ rating := int(p["rating"].(int64))
+
+ valve := &valve{
+ Type: typ,
+ ID: id,
+ Size: size,
+ Rating: rating,
+ }
+
+ o.parts = append(o.parts, valve)
+
+ case "pipe":
+
+ length := float32(p["length"].(float64))
+ diameter := int(p["diameter"].(int64))
+
+ pipe := &pipe{
+ Type: typ,
+ ID: id,
+ Length: length,
+ Diameter: diameter,
+ }
+
+ o.parts = append(o.parts, pipe)
+
+ case "cable":
+
+ length := int(p["length"].(int64))
+ rating := float32(p["rating"].(float64))
+
+ cable := &cable{
+ Type: typ,
+ ID: id,
+ Length: length,
+ Rating: rating,
+ }
+
+ o.parts = append(o.parts, cable)
+
+ }
+ }
+
+ return nil
+}
+
+type parts []part
+
+type part interface {
+ Name() string
+}
+
+type valve struct {
+ Type string
+ ID string
+ Size float32
+ Rating int
+}
+
+func (v *valve) Name() string {
+ return fmt.Sprintf("VALVE: %s", v.ID)
+}
+
+type pipe struct {
+ Type string
+ ID string
+ Length float32
+ Diameter int
+}
+
+func (p *pipe) Name() string {
+ return fmt.Sprintf("PIPE: %s", p.ID)
+}
+
+type cable struct {
+ Type string
+ ID string
+ Length int
+ Rating float32
+}
+
+func (c *cable) Name() string {
+ return fmt.Sprintf("CABLE: %s", c.ID)
+}