diff --git a/decode.go b/decode.go index 74b23dfc..e24f0c5d 100644 --- a/decode.go +++ b/decode.go @@ -320,13 +320,13 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { md.decoded[md.context.add(k).String()] = struct{}{} md.context = append(md.context, k) - rvkey := indirect(reflect.New(rv.Type().Key())) rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) if err := md.unify(v, rvval); err != nil { return err } md.context = md.context[0 : len(md.context)-1] + rvkey := indirect(reflect.New(rv.Type().Key())) rvkey.SetString(k) rv.SetMapIndex(rvkey, rvval) } diff --git a/decode_test.go b/decode_test.go index 66e04cfe..c89b572c 100644 --- a/decode_test.go +++ b/decode_test.go @@ -720,25 +720,23 @@ func TestDecodeDatetime(t *testing.T) { } func TestMetaDotConflict(t *testing.T) { - // See comment in the metaTest map in toml_test.go - t.Skip() - var m map[string]interface{} meta, err := Decode(` "a.b" = "str" a.b = 1 + "" = 2 `, &m) if err != nil { t.Fatal(err) } - want := "a.b=String; a|b=Integer" + want := `"a.b"=String; a.b=Integer; ""=Integer` have := "" for i, k := range meta.Keys() { if i > 0 { have += "; " } - have += strings.Join(k, "|") + "=" + meta.Type(k...) + have += k.String() + "=" + meta.Type(k...) } if have != want { t.Errorf("\nhave: %s\nwant: %s", have, want) diff --git a/encode.go b/encode.go index 9e5126f5..dee4e6d3 100644 --- a/encode.go +++ b/encode.go @@ -299,7 +299,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { continue } enc.newline() - enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) + enc.wf("%s[[%s]]", enc.indentStr(key), key) enc.newline() enc.eMapOrStruct(key, trv, false) } @@ -312,7 +312,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) { enc.newline() } if len(key) > 0 { - enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) + enc.wf("%s[%s]", enc.indentStr(key), key) enc.newline() } enc.eMapOrStruct(key, rv, false) diff --git a/meta.go b/meta.go index 49aed3fd..868619fb 100644 --- a/meta.go +++ b/meta.go @@ -10,11 +10,12 @@ import ( // It allows checking if a key is defined in the TOML data, whether any keys // were undecoded, and the TOML type of a key. type MetaData struct { + context Key // Used only during decoding. + mapping map[string]interface{} types map[string]tomlType keys []Key decoded map[string]struct{} - context Key // Used only during decoding. } // IsDefined reports if the key exists in the TOML data. @@ -49,8 +50,7 @@ func (md *MetaData) IsDefined(key ...string) bool { // Type will return the empty string if given an empty key or a key that does // not exist. Keys are case sensitive. func (md *MetaData) Type(key ...string) string { - fullkey := strings.Join(key, ".") - if typ, ok := md.types[fullkey]; ok { + if typ, ok := md.types[Key(key).String()]; ok { return typ.typeString() } return "" @@ -92,12 +92,10 @@ func (md *MetaData) Undecoded() []Key { // values of this type. type Key []string -func (k Key) String() string { return strings.Join(k, ".") } - -func (k Key) maybeQuotedAll() string { - var ss []string +func (k Key) String() string { + ss := make([]string, len(k)) for i := range k { - ss = append(ss, k.maybeQuoted(i)) + ss[i] = k.maybeQuoted(i) } return strings.Join(ss, ".") } @@ -106,16 +104,11 @@ func (k Key) maybeQuoted(i int) string { if k[i] == "" { return `""` } - quote := false for _, c := range k[i] { if !isBareKeyChar(c) { - quote = true - break + return `"` + dblQuotedReplacer.Replace(k[i]) + `"` } } - if quote { - return `"` + dblQuotedReplacer.Replace(k[i]) + `"` - } return k[i] } diff --git a/parse.go b/parse.go index f9e13e5f..618cbe4d 100644 --- a/parse.go +++ b/parse.go @@ -530,8 +530,8 @@ func (p *parser) addContext(key Key, array bool) { // set calls setValue and setType. func (p *parser) set(key string, val interface{}, typ tomlType) { - p.setValue(p.currentKey, val) - p.setType(p.currentKey, typ) + p.setValue(key, val) + p.setType(key, typ) } // setValue sets the given key to the given value in the current context. @@ -590,8 +590,8 @@ func (p *parser) setValue(key string, value interface{}) { hash[key] = value } -// setType sets the type of a particular value at a given key. -// It should be called immediately AFTER setValue. +// setType sets the type of a particular value at a given key. It should be +// called immediately AFTER setValue. // // Note that if `key` is empty, then the type given will be applied to the // current context (which is either a table or an array of tables). @@ -601,6 +601,12 @@ func (p *parser) setType(key string, typ tomlType) { if len(key) > 0 { // allow type setting for hashes keyContext = append(keyContext, key) } + // Special case to make empty keys ("" = 1) work. + // Without it it will set "" rather than `""`. + // TODO: why is this needed? And why is this only needed here? + if len(keyContext) == 0 { + keyContext = Key{""} + } p.types[keyContext.String()] = typ } @@ -678,7 +684,7 @@ func stripEscapedNewlines(s string) string { } func (p *parser) replaceEscapes(it item, str string) string { - var replaced []rune + replaced := make([]rune, 0, len(str)) s := []byte(str) r := 0 for r < len(s) { diff --git a/toml_test.go b/toml_test.go index 7337e5e3..cea4545f 100644 --- a/toml_test.go +++ b/toml_test.go @@ -31,75 +31,75 @@ var errorTests = map[string][]string{ // Test metadata; all keys listed as "keyname: type". var metaTests = map[string]string{ "implicit-and-explicit-after": ` - (a).(b).(c): Hash - (a).(b).(c).(answer): Integer - (a): Hash - (a).(better): Integer + a.b.c: Hash + a.b.c.answer: Integer + a: Hash + a.better: Integer `, "implicit-and-explicit-before": ` - (a): Hash - (a).(better): Integer - (a).(b).(c): Hash - (a).(b).(c).(answer): Integer + a: Hash + a.better: Integer + a.b.c: Hash + a.b.c.answer: Integer `, "key/case-sensitive": ` - (sectioN): String - (section): Hash - (section).(name): String - (section).(NAME): String - (section).(Name): String - (Section): Hash - (Section).(name): String - (Section).(μ): String - (Section).(Μ): String - (Section).(M): String + sectioN: String + section: Hash + section.name: String + section.NAME: String + section.Name: String + Section: Hash + Section.name: String + Section."μ": String + Section."Μ": String + Section.M: String `, "key/dotted": ` - (name).(first): String - (name).(last): String - (many).(dots).(here).(dot).(dot).(dot): Integer - (count).(a): Integer - (count).(b): Integer - (count).(c): Integer - (count).(d): Integer - (count).(e): Integer - (count).(f): Integer - (count).(g): Integer - (count).(h): Integer - (count).(i): Integer - (count).(j): Integer - (count).(k): Integer - (count).(l): Integer - (tbl): Hash - (tbl).(a).(b).(c): Float - (a).(few).(dots): Hash - (a).(few).(dots).(polka).(dot): String - (a).(few).(dots).(polka).(dance-with): String - (arr): ArrayHash - (arr).(a).(b).(c): Integer - (arr).(a).(b).(d): Integer - (arr): ArrayHash - (arr).(a).(b).(c): Integer - (arr).(a).(b).(d): Integer + name.first: String + name.last: String + many.dots.here.dot.dot.dot: Integer + count.a: Integer + count.b: Integer + count.c: Integer + count.d: Integer + count.e: Integer + count.f: Integer + count.g: Integer + count.h: Integer + count.i: Integer + count.j: Integer + count.k: Integer + count.l: Integer + tbl: Hash + tbl.a.b.c: Float + a.few.dots: Hash + a.few.dots.polka.dot: String + a.few.dots.polka.dance-with: String + arr: ArrayHash + arr.a.b.c: Integer + arr.a.b.d: Integer + arr: ArrayHash + arr.a.b.c: Integer + arr.a.b.d: Integer `, "key/empty": ` - (): String + "": String `, "key/quoted-dots": ` - (plain): Integer - (with.dot): Integer - (plain_table): Hash - (plain_table).(plain): Integer - (plain_table).(with.dot): Integer - (table).(withdot): Hash - (table).(withdot).(plain): Integer - (table).(withdot).(key.with.dots): Integer + plain: Integer + "with.dot": Integer + plain_table: Hash + plain_table.plain: Integer + plain_table."with.dot": Integer + table.withdot: Hash + table.withdot.plain: Integer + table.withdot."key.with.dots": Integer `, "key/space": ` - (a b): Integer + "a b": Integer `, "key/special-chars": "\n" + - "(~!@$^&*()_+-`1234567890[]|/?><.,;:'): Integer\n", + "\"~!@$^&*()_+-`1234567890[]|/?><.,;:'\": Integer\n", // TODO: "(albums): Hash" is missing; the problem is that this is an // "implied key", which is recorded in the parser in implicits, rather than @@ -121,20 +121,9 @@ var metaTests = map[string]string{ // (a).(better): Integer // // So if we want to add "(a).(b): Hash", where should this be in the order? - // - // Related problem: keys are stored as strings, rather than keys (i.e. - // []string); this is a problem because the key: - // - // "foo.bar" = 42 - // foo.bar = 42 - // - // Will have the same string value, but are not identical! - // - // Hashes need to have comparable types in the key, and slices aren't; but - // we can use pointers. Or maybe store it as "key\x00val" in the map. "table/array-implicit": ` - (albums).(songs): ArrayHash - (albums).(songs).(name): String + albums.songs: ArrayHash + albums.songs.name: String `, // TODO: people and people.* listed many times; not entirely sure if that's @@ -143,93 +132,107 @@ var metaTests = map[string]string{ // It certainly causes problems, because keys is a slice, and types a map. // So if array entry 1 differs in type from array entry 2 then that won't be // recorded right. This related to the problem in the above comment. + // + // people: ArrayHash + // + // people[0]: Hash + // people[0].first_name: String + // people[0].last_name: String + // + // people[1]: Hash + // people[1].first_name: String + // people[1].last_name: String + // + // people[2]: Hash + // people[2].first_name: String + // people[2].last_name: String "table/array-many": ` - (people): ArrayHash - (people).(first_name): String - (people).(last_name): String - (people): ArrayHash - (people).(first_name): String - (people).(last_name): String - (people): ArrayHash - (people).(first_name): String - (people).(last_name): String + people: ArrayHash + people.first_name: String + people.last_name: String + people: ArrayHash + people.first_name: String + people.last_name: String + people: ArrayHash + people.first_name: String + people.last_name: String `, "table/array-nest": ` - (albums): ArrayHash - (albums).(name): String - (albums).(songs): ArrayHash - (albums).(songs).(name): String - (albums).(songs): ArrayHash - (albums).(songs).(name): String - (albums): ArrayHash - (albums).(name): String - (albums).(songs): ArrayHash - (albums).(songs).(name): String - (albums).(songs): ArrayHash - (albums).(songs).(name): String + albums: ArrayHash + albums.name: String + albums.songs: ArrayHash + albums.songs.name: String + albums.songs: ArrayHash + albums.songs.name: String + albums: ArrayHash + albums.name: String + albums.songs: ArrayHash + albums.songs.name: String + albums.songs: ArrayHash + albums.songs.name: String `, "table/array-one": ` - (people): ArrayHash - (people).(first_name): String - (people).(last_name): String + people: ArrayHash + people.first_name: String + people.last_name: String `, "table/array-table-array": ` - (a): ArrayHash - (a).(b): ArrayHash - (a).(b).(c): Hash - (a).(b).(c).(d): String - (a).(b): ArrayHash - (a).(b).(c): Hash - (a).(b).(c).(d): String + a: ArrayHash + a.b: ArrayHash + a.b.c: Hash + a.b.c.d: String + a.b: ArrayHash + a.b.c: Hash + a.b.c.d: String `, "table/empty": ` - (a): Hash + a: Hash `, "table/keyword": ` - (true): Hash - (false): Hash - (inf): Hash - (nan): Hash + true: Hash + false: Hash + inf: Hash + nan: Hash `, "table/names": ` - (a).(b).(c): Hash - (a).(b.c): Hash - (a).(d.e): Hash - (a).( x ): Hash - (d).(e).(f): Hash - (g).(h).(i): Hash - (j).(ʞ).(l): Hash - (x).(1).(2): Hash + a.b.c: Hash + a."b.c": Hash + a."d.e": Hash + a." x ": Hash + d.e.f: Hash + g.h.i: Hash + j."ʞ".l: Hash + x.1.2: Hash `, "table/no-eol": ` - (table): Hash + table: Hash `, "table/sub-empty": ` - (a): Hash - (a).(b): Hash + a: Hash + a.b: Hash `, "table/whitespace": ` - (valid key): Hash + "valid key": Hash `, "table/with-literal-string": ` - (a): Hash - (a).("b"): Hash - (a).("b").(c): Hash - (a).("b").(c).(answer): Integer + a: Hash + a."\"b\"": Hash + a."\"b\"".c: Hash + a."\"b\"".c.answer: Integer `, "table/with-pound": ` - (key#group): Hash - (key#group).(answer): Integer + "key#group": Hash + "key#group".answer: Integer `, "table/with-single-quotes": ` - (a): Hash - (a).(b): Hash - (a).(b).(c): Hash - (a).(b).(c).(answer): Integer + a: Hash + a.b: Hash + a.b.c: Hash + a.b.c.answer: Integer `, "table/without-super": ` - (x).(y).(z).(w): Hash - (x): Hash + x.y.z.w: Hash + x: Hash `, } @@ -357,7 +360,7 @@ func testMeta(t *testing.T, test tomltest.Test) { if i > 0 { b.WriteByte('\n') } - fmt.Fprintf(b, "(%s): %s", strings.Join(k, ").("), meta.Type(k...)) + fmt.Fprintf(b, "%s: %s", k, meta.Type(k...)) } have := b.String()