Skip to content

Commit 7d0236f

Browse files
authored
Make sure quoted keys with dots work well (#333)
Using strings.Join(key, ".") is problematic as these: "foo.bar" = 42 foo.bar = 42 Will have the same string value, but are not identical! Always use maybeQuotedAll() in String(); I don't know when you would ever *not* want to use quoting.
1 parent ff0a3f8 commit 7d0236f

File tree

6 files changed

+156
-156
lines changed

6 files changed

+156
-156
lines changed

decode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,13 +320,13 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
320320
md.decoded[md.context.add(k).String()] = struct{}{}
321321
md.context = append(md.context, k)
322322

323-
rvkey := indirect(reflect.New(rv.Type().Key()))
324323
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
325324
if err := md.unify(v, rvval); err != nil {
326325
return err
327326
}
328327
md.context = md.context[0 : len(md.context)-1]
329328

329+
rvkey := indirect(reflect.New(rv.Type().Key()))
330330
rvkey.SetString(k)
331331
rv.SetMapIndex(rvkey, rvval)
332332
}

decode_test.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -720,25 +720,23 @@ func TestDecodeDatetime(t *testing.T) {
720720
}
721721

722722
func TestMetaDotConflict(t *testing.T) {
723-
// See comment in the metaTest map in toml_test.go
724-
t.Skip()
725-
726723
var m map[string]interface{}
727724
meta, err := Decode(`
728725
"a.b" = "str"
729726
a.b = 1
727+
"" = 2
730728
`, &m)
731729
if err != nil {
732730
t.Fatal(err)
733731
}
734732

735-
want := "a.b=String; a|b=Integer"
733+
want := `"a.b"=String; a.b=Integer; ""=Integer`
736734
have := ""
737735
for i, k := range meta.Keys() {
738736
if i > 0 {
739737
have += "; "
740738
}
741-
have += strings.Join(k, "|") + "=" + meta.Type(k...)
739+
have += k.String() + "=" + meta.Type(k...)
742740
}
743741
if have != want {
744742
t.Errorf("\nhave: %s\nwant: %s", have, want)

encode.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
299299
continue
300300
}
301301
enc.newline()
302-
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
302+
enc.wf("%s[[%s]]", enc.indentStr(key), key)
303303
enc.newline()
304304
enc.eMapOrStruct(key, trv, false)
305305
}
@@ -312,7 +312,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
312312
enc.newline()
313313
}
314314
if len(key) > 0 {
315-
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
315+
enc.wf("%s[%s]", enc.indentStr(key), key)
316316
enc.newline()
317317
}
318318
enc.eMapOrStruct(key, rv, false)

meta.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ import (
1010
// It allows checking if a key is defined in the TOML data, whether any keys
1111
// were undecoded, and the TOML type of a key.
1212
type MetaData struct {
13+
context Key // Used only during decoding.
14+
1315
mapping map[string]interface{}
1416
types map[string]tomlType
1517
keys []Key
1618
decoded map[string]struct{}
17-
context Key // Used only during decoding.
1819
}
1920

2021
// IsDefined reports if the key exists in the TOML data.
@@ -49,8 +50,7 @@ func (md *MetaData) IsDefined(key ...string) bool {
4950
// Type will return the empty string if given an empty key or a key that does
5051
// not exist. Keys are case sensitive.
5152
func (md *MetaData) Type(key ...string) string {
52-
fullkey := strings.Join(key, ".")
53-
if typ, ok := md.types[fullkey]; ok {
53+
if typ, ok := md.types[Key(key).String()]; ok {
5454
return typ.typeString()
5555
}
5656
return ""
@@ -92,12 +92,10 @@ func (md *MetaData) Undecoded() []Key {
9292
// values of this type.
9393
type Key []string
9494

95-
func (k Key) String() string { return strings.Join(k, ".") }
96-
97-
func (k Key) maybeQuotedAll() string {
98-
var ss []string
95+
func (k Key) String() string {
96+
ss := make([]string, len(k))
9997
for i := range k {
100-
ss = append(ss, k.maybeQuoted(i))
98+
ss[i] = k.maybeQuoted(i)
10199
}
102100
return strings.Join(ss, ".")
103101
}
@@ -106,16 +104,11 @@ func (k Key) maybeQuoted(i int) string {
106104
if k[i] == "" {
107105
return `""`
108106
}
109-
quote := false
110107
for _, c := range k[i] {
111108
if !isBareKeyChar(c) {
112-
quote = true
113-
break
109+
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
114110
}
115111
}
116-
if quote {
117-
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
118-
}
119112
return k[i]
120113
}
121114

parse.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,8 @@ func (p *parser) addContext(key Key, array bool) {
530530

531531
// set calls setValue and setType.
532532
func (p *parser) set(key string, val interface{}, typ tomlType) {
533-
p.setValue(p.currentKey, val)
534-
p.setType(p.currentKey, typ)
533+
p.setValue(key, val)
534+
p.setType(key, typ)
535535
}
536536

537537
// 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{}) {
590590
hash[key] = value
591591
}
592592

593-
// setType sets the type of a particular value at a given key.
594-
// It should be called immediately AFTER setValue.
593+
// setType sets the type of a particular value at a given key. It should be
594+
// called immediately AFTER setValue.
595595
//
596596
// Note that if `key` is empty, then the type given will be applied to the
597597
// current context (which is either a table or an array of tables).
@@ -601,6 +601,12 @@ func (p *parser) setType(key string, typ tomlType) {
601601
if len(key) > 0 { // allow type setting for hashes
602602
keyContext = append(keyContext, key)
603603
}
604+
// Special case to make empty keys ("" = 1) work.
605+
// Without it it will set "" rather than `""`.
606+
// TODO: why is this needed? And why is this only needed here?
607+
if len(keyContext) == 0 {
608+
keyContext = Key{""}
609+
}
604610
p.types[keyContext.String()] = typ
605611
}
606612

@@ -678,7 +684,7 @@ func stripEscapedNewlines(s string) string {
678684
}
679685

680686
func (p *parser) replaceEscapes(it item, str string) string {
681-
var replaced []rune
687+
replaced := make([]rune, 0, len(str))
682688
s := []byte(str)
683689
r := 0
684690
for r < len(s) {

0 commit comments

Comments
 (0)